Thanks to visit codestin.com
Credit goes to github.com

Skip to content

refactor: match StatusIndicator component with the new designs #16458

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

Merged
merged 8 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 21 additions & 29 deletions site/src/components/StatusIndicator/StatusIndicator.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,55 @@
import type { Meta, StoryObj } from "@storybook/react";
import { StatusIndicator } from "./StatusIndicator";
import { StatusIndicator, StatusIndicatorDot } from "./StatusIndicator";

const meta: Meta<typeof StatusIndicator> = {
title: "components/StatusIndicator",
component: StatusIndicator,
args: {},
args: {
children: (
<>
<StatusIndicatorDot />
Status
</>
),
},
};

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

export const Success: Story = {
args: {
color: "success",
},
};

export const SuccessOutline: Story = {
args: {
color: "success",
variant: "outlined",
},
};

export const Warning: Story = {
args: {
color: "warning",
variant: "success",
},
};

export const WarningOutline: Story = {
export const Failed: Story = {
args: {
color: "warning",
variant: "outlined",
variant: "failed",
},
};

export const Danger: Story = {
export const Inactive: Story = {
args: {
color: "danger",
variant: "inactive",
},
};

export const DangerOutline: Story = {
export const Warning: Story = {
args: {
color: "danger",
variant: "outlined",
variant: "warning",
},
};

export const Inactive: Story = {
export const Pending: Story = {
args: {
color: "inactive",
variant: "pending",
},
};

export const InactiveOutline: Story = {
export const Small: Story = {
args: {
color: "inactive",
variant: "outlined",
variant: "success",
size: "sm",
},
};
110 changes: 87 additions & 23 deletions site/src/components/StatusIndicator/StatusIndicator.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,97 @@
import { useTheme } from "@emotion/react";
import type { FC } from "react";
import type { ThemeRole } from "theme/roles";
import { type VariantProps, cva } from "class-variance-authority";
import { type FC, createContext, useContext } from "react";
import { cn } from "utils/cn";

interface StatusIndicatorProps {
color: ThemeRole;
variant?: "solid" | "outlined";
}
const statusIndicatorVariants = cva(
"font-medium inline-flex items-center gap-2",
{
variants: {
variant: {
success: "text-content-success",
failed: "text-content-destructive",
inactive: "text-highlight-grey",
warning: "text-content-warning",
pending: "text-highlight-sky",
},
size: {
sm: "text-xs",
md: "text-sm",
},
},
defaultVariants: {
variant: "success",
size: "md",
},
},
);

type StatusIndicatorContextValue = VariantProps<typeof statusIndicatorVariants>;

const StatusIndicatorContext = createContext<StatusIndicatorContextValue>({});

export interface StatusIndicatorProps
extends React.HTMLAttributes<HTMLDivElement>,
StatusIndicatorContextValue {}

export const StatusIndicator: FC<StatusIndicatorProps> = ({
color,
variant = "solid",
size,
variant,
className,
...props
}) => {
const theme = useTheme();
return (
<StatusIndicatorContext.Provider value={{ size, variant }}>
<div
className={cn(statusIndicatorVariants({ variant, size }), className)}
{...props}
/>
</StatusIndicatorContext.Provider>
);
};

const dotVariants = cva("rounded-full inline-block border-4 border-solid", {
variants: {
variant: {
success: "bg-content-success border-surface-green",
failed: "bg-content-destructive border-surface-destructive",
inactive: "bg-highlight-grey border-surface-grey",
warning: "bg-content-warning border-surface-orange",
pending: "bg-highlight-sky border-surface-sky",
},
size: {
sm: "size-3 border-4",
md: "size-4 border-4",
},
},
defaultVariants: {
variant: "success",
size: "md",
},
});

export interface StatusIndicatorDotProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof dotVariants> {}

export const StatusIndicatorDot: FC<StatusIndicatorDotProps> = ({
className,
// We allow the size and variant to be overridden directly by the component.
// This allows StatusIndicatorDot to be used alone.
size,
variant,
...props
}) => {
const { size: ctxSize, variant: ctxVariant } = useContext(
StatusIndicatorContext,
);

return (
<div
css={[
{
height: 8,
width: 8,
borderRadius: 4,
},
variant === "solid" && {
backgroundColor: theme.roles[color].fill.solid,
},
variant === "outlined" && {
border: `1px solid ${theme.roles[color].outline}`,
},
]}
className={cn(
dotVariants({ variant: variant ?? ctxVariant, size: size ?? ctxSize }),
className,
)}
{...props}
/>
);
};
18 changes: 15 additions & 3 deletions site/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,28 @@
--content-invert: 0 0% 98%;
--content-disabled: 240 5% 65%;
--content-success: 142 72% 29%;
--content-danger: 0 84% 60%;
--content-warning: 27 96% 61%;
--content-destructive: 0 84% 60%;
--surface-primary: 0 0% 98%;
--surface-secondary: 240 5% 96%;
--surface-tertiary: 240 6% 90%;
--surface-quaternary: 240 5% 84%;
--surface-invert-primary: 240 4% 16%;
--surface-invert-secondary: 240 5% 26%;
--surface-destructive: 0 93% 94%;
--surface-green: 141 79% 85%;
--surface-grey: 240 5% 96%;
--surface-orange: 34 100% 92%;
--surface-sky: 201 94% 86%;
--border-default: 240 6% 90%;
--border-success: 142 76% 36%;
--border-destructive: 0 84% 60%;
--overlay-default: 240 5% 84% / 80%;
--radius: 0.5rem;
--highlight-purple: 262 83% 58%;
--highlight-green: 143 64% 24%;
--highlight-grey: 240 5% 65%;
--highlight-sky: 201 90% 27%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
Expand All @@ -45,21 +51,27 @@
--content-invert: 240 10% 4%;
--content-disabled: 240 5% 26%;
--content-success: 142 76% 36%;
--content-danger: 0 91% 71%;
--content-warning: 27 96% 61%;
--content-warning: 31 97% 72%;
--content-destructive: 0 91% 71%;
--surface-primary: 240 10% 4%;
--surface-secondary: 240 6% 10%;
--surface-tertiary: 240 4% 16%;
--surface-quaternary: 240 5% 26%;
--surface-invert-primary: 240 6% 90%;
--surface-invert-secondary: 240 5% 65%;
--surface-destructive: 0 75% 15%;
--surface-green: 145 80% 10%;
--surface-grey: 240 6% 10%;
--surface-orange: 13 81% 15%;
--surface-sky: 204 80% 16%;
--border-default: 240 4% 16%;
--border-success: 142 76% 36%;
--border-destructive: 0 91% 71%;
--overlay-default: 240 10% 4% / 80%;
--highlight-purple: 252 95% 85%;
--highlight-green: 141 79% 85%;
--highlight-grey: 240 4% 46%;
--highlight-sky: 198 93% 60%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
Expand Down
4 changes: 2 additions & 2 deletions site/src/modules/provisioners/ProvisionerGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from "components/HelpTooltip/HelpTooltip";
import { Pill } from "components/Pill/Pill";
import { Stack } from "components/Stack/Stack";
import { StatusIndicator } from "components/StatusIndicator/StatusIndicator";
import { StatusIndicatorDot } from "components/StatusIndicator/StatusIndicator";
import {
Popover,
PopoverContent,
Expand Down Expand Up @@ -127,7 +127,7 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
}}
>
<div css={{ display: "flex", alignItems: "center", gap: 16 }}>
<StatusIndicator color={hasWarning ? "warning" : "success"} />
<StatusIndicatorDot variant={hasWarning ? "warning" : "success"} />
<div
css={{
display: "flex",
Expand Down
1 change: 1 addition & 0 deletions site/src/pages/CreateTokenPage/CreateTokenForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export const CreateTokenForm: FC<CreateTokenFormProps> = ({
</FormFields>
</FormSection>
<FormSection
data-chromatic="ignore"
title="Expiration"
description={
form.values.lifetime
Expand Down
6 changes: 0 additions & 6 deletions site/src/pages/CreateTokenPage/CreateTokenPage.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ const meta: Meta<typeof CreateTokenPage> = {
},
],
},
decorators: [
(Story) => {
Date.now = () => new Date("01/01/2014").getTime();
return <Story />;
},
],
};

export default meta;
Expand Down
11 changes: 7 additions & 4 deletions site/src/pages/UsersPage/UsersFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import {
type UseFilterMenuOptions,
useFilterMenu,
} from "components/Filter/menu";
import { StatusIndicator } from "components/StatusIndicator/StatusIndicator";
import {
StatusIndicator,
StatusIndicatorDot,
} from "components/StatusIndicator/StatusIndicator";
import type { FC } from "react";
import { docs } from "utils/docs";

Expand All @@ -24,17 +27,17 @@ export const useStatusFilterMenu = ({
{
value: "active",
label: "Active",
startIcon: <StatusIndicator color="success" />,
startIcon: <StatusIndicatorDot variant="success" />,
},
{
value: "dormant",
label: "Dormant",
startIcon: <StatusIndicator color="warning" />,
startIcon: <StatusIndicatorDot variant="warning" />,
},
{
value: "suspended",
label: "Suspended",
startIcon: <StatusIndicator color="inactive" />,
startIcon: <StatusIndicatorDot variant="inactive" />,
},
];
return useFilterMenu({
Expand Down
12 changes: 6 additions & 6 deletions site/src/pages/WorkspacesPage/LastUsed.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useTheme } from "@emotion/react";
import { Stack } from "components/Stack/Stack";
import { StatusIndicator } from "components/StatusIndicator/StatusIndicator";
import { StatusIndicatorDot } from "components/StatusIndicator/StatusIndicator";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { useTime } from "hooks/useTime";
Expand All @@ -18,19 +18,19 @@ export const LastUsed: FC<LastUsedProps> = ({ lastUsedAt }) => {
const t = dayjs(lastUsedAt);
const now = dayjs();
let message = t.fromNow();
let circle = <StatusIndicator color="info" variant="outlined" />;
let circle = <StatusIndicatorDot variant="inactive" />;

if (t.isAfter(now.subtract(1, "hour"))) {
circle = <StatusIndicator color="success" />;
circle = <StatusIndicatorDot variant="success" />;
// Since the agent reports on a 10m interval,
// the last_used_at can be inaccurate when recent.
message = "Now";
} else if (t.isAfter(now.subtract(3, "day"))) {
circle = <StatusIndicator color="info" />;
circle = <StatusIndicatorDot variant="pending" />;
} else if (t.isAfter(now.subtract(1, "month"))) {
circle = <StatusIndicator color="warning" />;
circle = <StatusIndicatorDot variant="warning" />;
} else if (t.isAfter(now.subtract(100, "year"))) {
circle = <StatusIndicator color="error" />;
circle = <StatusIndicatorDot variant="failed" />;
} else {
message = "Never";
}
Expand Down
Loading
Loading