Skip to content

feat: add early access badges for dynamic parameters #18114

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 3 commits into from
May 30, 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
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,30 @@ const meta: Meta<typeof FeatureStageBadge> = {
export default meta;
type Story = StoryObj<typeof FeatureStageBadge>;

export const MediumBeta: Story = {
export const SmallBeta: Story = {
args: {
size: "md",
size: "sm",
contentType: "beta",
},
};

export const SmallBeta: Story = {
export const MediumBeta: Story = {
args: {
size: "sm",
size: "md",
contentType: "beta",
},
};

export const LargeBeta: Story = {
export const SmallEarlyAccess: Story = {
args: {
size: "lg",
size: "sm",
contentType: "early_access",
},
};

export const MediumExperimental: Story = {
export const MediumEarlyAccess: Story = {
args: {
size: "md",
contentType: "experimental",
contentType: "early_access",
},
};
142 changes: 43 additions & 99 deletions site/src/components/FeatureStageBadge/FeatureStageBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,142 +1,86 @@
import type { Interpolation, Theme } from "@emotion/react";
import Link from "@mui/material/Link";
import { visuallyHidden } from "@mui/utils";
import { HelpTooltipContent } from "components/HelpTooltip/HelpTooltip";
import { Popover, PopoverTrigger } from "components/deprecated/Popover/Popover";
import { Link } from "components/Link/Link";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import type { FC, HTMLAttributes, ReactNode } from "react";
import { cn } from "utils/cn";
import { docs } from "utils/docs";

/**
* All types of feature that we are currently supporting. Defined as record to
* ensure that we can't accidentally make typos when writing the badge text.
*/
export const featureStageBadgeTypes = {
early_access: "early access",
beta: "beta",
experimental: "experimental",
} as const satisfies Record<string, ReactNode>;

type FeatureStageBadgeProps = Readonly<
Omit<HTMLAttributes<HTMLSpanElement>, "children"> & {
contentType: keyof typeof featureStageBadgeTypes;
labelText?: string;
size?: "sm" | "md" | "lg";
showTooltip?: boolean;
size?: "sm" | "md";
}
>;

const badgeColorClasses = {
early_access: "bg-surface-orange text-content-warning",
beta: "bg-surface-sky text-highlight-sky",
} as const;

const badgeSizeClasses = {
sm: "text-xs font-medium px-2 py-1",
md: "text-base px-2 py-1",
} as const;

export const FeatureStageBadge: FC<FeatureStageBadgeProps> = ({
contentType,
labelText = "",
size = "md",
showTooltip = true, // This is a temporary until the deprecated popover is removed
className,
...delegatedProps
}) => {
const colorClasses = badgeColorClasses[contentType];
const sizeClasses = badgeSizeClasses[size];

return (
<Popover mode="hover">
<PopoverTrigger>
{({ isOpen }) => (
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger asChild>
<span
css={[
styles.badge,
size === "sm" && styles.badgeSmallText,
size === "lg" && styles.badgeLargeText,
isOpen && styles.badgeHover,
]}
className={cn(
"block max-w-fit cursor-default flex-shrink-0 leading-none whitespace-nowrap border rounded-md transition-colors duration-200 ease-in-out bg-transparent border-solid border-transparent",
sizeClasses,
colorClasses,
className,
)}
{...delegatedProps}
>
<span style={visuallyHidden}> (This is a</span>
<span className="sr-only"> (This is a</span>
<span className="first-letter:uppercase">
{labelText && `${labelText} `}
{featureStageBadgeTypes[contentType]}
</span>
<span style={visuallyHidden}> feature)</span>
<span className="sr-only"> feature)</span>
</span>
)}
</PopoverTrigger>

{showTooltip && (
<HelpTooltipContent
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
transformOrigin={{ vertical: "top", horizontal: "center" }}
>
<p css={styles.tooltipDescription}>
</TooltipTrigger>
<TooltipContent align="start" className="max-w-xs text-sm">
<p className="m-0">
This feature has not yet reached general availability (GA).
</p>

<Link
href={docs("/install/releases/feature-stages")}
target="_blank"
rel="noreferrer"
css={styles.tooltipLink}
className="font-semibold"
>
Learn about feature stages
<span style={visuallyHidden}> (link opens in new tab)</span>
<span className="sr-only"> (link opens in new tab)</span>
</Link>
</HelpTooltipContent>
)}
</Popover>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
};

const styles = {
badge: (theme) => ({
// Base type is based on a span so that the element can be placed inside
// more types of HTML elements without creating invalid markdown, but we
// still want the default display behavior to be div-like
display: "block",
maxWidth: "fit-content",

// Base style assumes that medium badges will be the default
fontSize: "0.75rem",

cursor: "default",
flexShrink: 0,
padding: "4px 8px",
lineHeight: 1,
whiteSpace: "nowrap",
border: `1px solid ${theme.branding.featureStage.border}`,
color: theme.branding.featureStage.text,
backgroundColor: theme.branding.featureStage.background,
borderRadius: "6px",
transition:
"color 0.2s ease-in-out, border-color 0.2s ease-in-out, background-color 0.2s ease-in-out",
}),

badgeHover: (theme) => ({
color: theme.branding.featureStage.hover.text,
borderColor: theme.branding.featureStage.hover.border,
backgroundColor: theme.branding.featureStage.hover.background,
}),

badgeLargeText: {
fontSize: "1rem",
},

badgeSmallText: {
// Have to beef up font weight so that the letters still maintain the
// same relative thickness as all our other main UI text
fontWeight: 500,
fontSize: "0.625rem",
},

tooltipTitle: (theme) => ({
color: theme.palette.text.primary,
fontWeight: 600,
fontFamily: "inherit",
fontSize: 18,
margin: 0,
lineHeight: 1,
paddingBottom: "8px",
}),

tooltipDescription: {
margin: 0,
lineHeight: 1.4,
paddingBottom: "8px",
},

tooltipLink: {
fontWeight: 600,
lineHeight: 1.2,
},
} as const satisfies Record<string, Interpolation<Theme>>;
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import type { FriendlyDiagnostic, PreviewParameter } from "api/typesGenerated";
import { Alert } from "components/Alert/Alert";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Avatar } from "components/Avatar/Avatar";
import { Badge } from "components/Badge/Badge";
import { Button } from "components/Button/Button";
import { FeatureStageBadge } from "components/FeatureStageBadge/FeatureStageBadge";
import { Input } from "components/Input/Input";
import { Label } from "components/Label/Label";
import { Link } from "components/Link/Link";
import { Pill } from "components/Pill/Pill";
import {
Select,
SelectContent,
Expand Down Expand Up @@ -353,21 +353,39 @@ export const CreateWorkspacePageViewExperimental: FC<
</div>
<div className="flex flex-col gap-6 max-w-screen-md mx-auto">
<header className="flex flex-col items-start gap-3 mt-10">
<div className="flex items-center gap-2">
<Avatar
variant="icon"
size="md"
src={template.icon}
fallback={template.name}
/>
<p className="text-base font-medium m-0">
{template.display_name.length > 0
? template.display_name
: template.name}
</p>
<div className="flex items-center gap-2 justify-between w-full">
<span className="flex items-center gap-2">
<Avatar
variant="icon"
size="md"
src={template.icon}
fallback={template.name}
/>
<p className="text-base font-medium m-0">
{template.display_name.length > 0
? template.display_name
: template.name}
</p>
{template.deprecated && (
<Badge variant="warning" size="sm">
Deprecated
</Badge>
)}
</span>
{experimentalFormContext && (
<Button
size="sm"
variant="outline"
onClick={experimentalFormContext.toggleOptedOut}
>
<Undo2 />
Classic workspace creation
</Button>
)}
</div>
<span className="flex flex-row items-center gap-2">
<h1 className="text-3xl font-semibold m-0">New workspace</h1>

<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger asChild>
Expand All @@ -389,19 +407,11 @@ export const CreateWorkspacePageViewExperimental: FC<
</Tooltip>
</TooltipProvider>
</span>

{template.deprecated && <Pill type="warning">Deprecated</Pill>}

{experimentalFormContext && (
<Button
size="sm"
variant="outline"
onClick={experimentalFormContext.toggleOptedOut}
>
<Undo2 />
Use the classic workspace creation flow
</Button>
)}
<FeatureStageBadge
contentType={"early_access"}
size="sm"
labelText="Dynamic parameters"
/>
</header>

<form
Expand Down Expand Up @@ -555,7 +565,7 @@ export const CreateWorkspacePageViewExperimental: FC<
<div className="flex flex-col gap-2">
<div className="flex gap-2 items-center">
<Label className="text-sm">Preset</Label>
<FeatureStageBadge contentType={"beta"} size="md" />
<FeatureStageBadge contentType={"beta"} size="sm" />
</div>
<div className="flex flex-col gap-4">
<div className="max-w-lg">
Expand Down
2 changes: 1 addition & 1 deletion site/src/pages/UserSettingsPage/Section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const Section: FC<SectionProps> = ({
{featureStage && (
<FeatureStageBadge
contentType={featureStage}
size="lg"
size="md"
css={{ marginBottom: "5px" }}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,18 @@ export const WorkspaceParametersPageView: FC<
return (
<div className="flex flex-col gap-10">
<header className="flex flex-col items-start gap-2">
<span className="flex flex-row justify-between items-center gap-2">
<span className="flex flex-row justify-between w-full items-center gap-2">
<h1 className="text-3xl m-0">Workspace parameters</h1>
{experimentalFormContext && (
<ShadcnButton
size="sm"
variant="outline"
onClick={experimentalFormContext.toggleOptedOut}
>
Try out the new workspace parameters ✨
</ShadcnButton>
)}
</span>
{experimentalFormContext && (
<ShadcnButton
size="sm"
variant="outline"
onClick={experimentalFormContext.toggleOptedOut}
>
Try out the new workspace parameters ✨
</ShadcnButton>
)}
</header>

{submitError && !isApiValidationError(submitError) ? (
Expand Down
Loading
Loading