From f843f23cb7023ffa872de0d8511124078cbd3761 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 12 Sep 2024 21:28:08 +0000 Subject: [PATCH 01/17] chore: finish draft work for FeatureBadge component --- .../FeatureBadge/FeatureBadge.stories.tsx | 22 ++++++ .../components/FeatureBadge/FeatureBadge.tsx | 67 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 site/src/components/FeatureBadge/FeatureBadge.stories.tsx create mode 100644 site/src/components/FeatureBadge/FeatureBadge.tsx diff --git a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx new file mode 100644 index 0000000000000..32072c3facaad --- /dev/null +++ b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx @@ -0,0 +1,22 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { FeatureBadge } from "./FeatureBadge"; + +const meta: Meta = { + title: "components/FeatureBadge", + component: FeatureBadge, +}; + +export default meta; +type Story = StoryObj; + +export const Small: Story = { + args: { + size: "sm", + }, +}; + +export const Medium: Story = { + args: { + size: "md", + }, +}; diff --git a/site/src/components/FeatureBadge/FeatureBadge.tsx b/site/src/components/FeatureBadge/FeatureBadge.tsx new file mode 100644 index 0000000000000..73ef11d7f2fa3 --- /dev/null +++ b/site/src/components/FeatureBadge/FeatureBadge.tsx @@ -0,0 +1,67 @@ +import type { Interpolation, Theme } from "@emotion/react"; +import type { FC, HTMLAttributes, ReactNode } from "react"; + +/** + * 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. + */ +const featureBadgeTypes = { + beta: "beta", + earlyAccess: "early access", +} as const satisfies Record; + +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", + flexShrink: 0, + padding: "8px 4px", + border: `1px solid ${theme.palette.divider}`, + color: theme.palette.text.secondary, + backgroundColor: theme.palette.background.default, + borderRadius: "6px", + + // Base style assumes that small badges will be the default + fontSize: "0.75rem", + }), + + highlighted: (theme) => ({ + color: theme.palette.text.primary, + borderColor: theme.palette.text.primary, + }), + + mediumText: { + fontSize: "1rem", + }, +} as const satisfies Record>; + +type FeatureBadgeProps = Readonly< + Omit, "children"> & { + type: keyof typeof featureBadgeTypes; + size?: "sm" | "md"; + isHighlighted?: boolean; + } +>; + +export const FeatureBadge: FC = ({ + type, + size = "sm", + isHighlighted = false, + ...delegatedProps +}) => { + return ( + + {featureBadgeTypes[type]} + + ); +}; From 43874a4a239aa8282d255a10f42e0d817f88de28 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 12 Sep 2024 21:36:42 +0000 Subject: [PATCH 02/17] fix: add visually-hidden helper text for screen readers --- site/src/components/FeatureBadge/FeatureBadge.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/site/src/components/FeatureBadge/FeatureBadge.tsx b/site/src/components/FeatureBadge/FeatureBadge.tsx index 73ef11d7f2fa3..7c10a404858a4 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.tsx @@ -1,4 +1,5 @@ import type { Interpolation, Theme } from "@emotion/react"; +import { visuallyHidden } from "@mui/utils"; import type { FC, HTMLAttributes, ReactNode } from "react"; /** @@ -61,7 +62,9 @@ export const FeatureBadge: FC = ({ ]} {...delegatedProps} > + (This feature is {featureBadgeTypes[type]} + ) ); }; From fb70781f771ed2f6190674ddb55883fb283fc691 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 12 Sep 2024 21:39:55 +0000 Subject: [PATCH 03/17] chore: add stories for highlighted state --- .../FeatureBadge/FeatureBadge.stories.tsx | 14 ++++++++++++++ site/src/components/FeatureBadge/FeatureBadge.tsx | 6 +++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx index 32072c3facaad..3db8210787d2a 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx @@ -20,3 +20,17 @@ export const Medium: Story = { size: "md", }, }; + +export const HighlightedSmall: Story = { + args: { + size: "sm", + highlighted: true, + }, +}; + +export const HighlightedMedium: Story = { + args: { + size: "md", + highlighted: true, + }, +}; diff --git a/site/src/components/FeatureBadge/FeatureBadge.tsx b/site/src/components/FeatureBadge/FeatureBadge.tsx index 7c10a404858a4..dd63ea48a4a43 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.tsx @@ -43,14 +43,14 @@ type FeatureBadgeProps = Readonly< Omit, "children"> & { type: keyof typeof featureBadgeTypes; size?: "sm" | "md"; - isHighlighted?: boolean; + highlighted?: boolean; } >; export const FeatureBadge: FC = ({ type, size = "sm", - isHighlighted = false, + highlighted = false, ...delegatedProps }) => { return ( @@ -58,7 +58,7 @@ export const FeatureBadge: FC = ({ css={[ styles.badge, size === "md" && styles.mediumText, - isHighlighted && styles.highlighted, + highlighted && styles.highlighted, ]} {...delegatedProps} > From c2470f97f5868964494e4667c785f2a1d3f2ac14 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 12 Sep 2024 21:50:49 +0000 Subject: [PATCH 04/17] fix: update base styles --- .../components/FeatureBadge/FeatureBadge.stories.tsx | 3 +++ site/src/components/FeatureBadge/FeatureBadge.tsx | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx index 3db8210787d2a..8b5236eb774a1 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx @@ -4,6 +4,9 @@ import { FeatureBadge } from "./FeatureBadge"; const meta: Meta = { title: "components/FeatureBadge", component: FeatureBadge, + args: { + type: "beta", + }, }; export default meta; diff --git a/site/src/components/FeatureBadge/FeatureBadge.tsx b/site/src/components/FeatureBadge/FeatureBadge.tsx index dd63ea48a4a43..0163292133a67 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.tsx @@ -17,16 +17,19 @@ const styles = { // more types of HTML elements without creating invalid markdown, but we // still want the default display behavior to be div-like display: "block", + + // Base style assumes that small badges will be the default + fontSize: "0.75rem", + maxWidth: "fit-content", flexShrink: 0, - padding: "8px 4px", + padding: "4px 8px", + lineHeight: 1, + whiteSpace: "nowrap", border: `1px solid ${theme.palette.divider}`, color: theme.palette.text.secondary, backgroundColor: theme.palette.background.default, borderRadius: "6px", - - // Base style assumes that small badges will be the default - fontSize: "0.75rem", }), highlighted: (theme) => ({ From 693946f78df903c8e6b815e5500be766dcc4e87e Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 12 Sep 2024 21:54:48 +0000 Subject: [PATCH 05/17] chore: remove debug display option --- site/src/components/FeatureBadge/FeatureBadge.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/site/src/components/FeatureBadge/FeatureBadge.tsx b/site/src/components/FeatureBadge/FeatureBadge.tsx index 0163292133a67..0872a4b82faa4 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.tsx @@ -8,7 +8,6 @@ import type { FC, HTMLAttributes, ReactNode } from "react"; */ const featureBadgeTypes = { beta: "beta", - earlyAccess: "early access", } as const satisfies Record; const styles = { From 129613b743b206eeeac2bbf4ffc630dd891402cf Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 13 Sep 2024 19:46:43 +0000 Subject: [PATCH 06/17] chore: update Popover to propagate events --- site/src/components/Popover/Popover.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/site/src/components/Popover/Popover.tsx b/site/src/components/Popover/Popover.tsx index 7db3c4eda1799..43db214b0f097 100644 --- a/site/src/components/Popover/Popover.tsx +++ b/site/src/components/Popover/Popover.tsx @@ -1,10 +1,10 @@ import MuiPopover, { type PopoverProps as MuiPopoverProps, - // biome-ignore lint/nursery/noRestrictedImports: Used as base component } from "@mui/material/Popover"; import { type FC, type HTMLAttributes, + type PointerEvent, type ReactElement, type ReactNode, type RefObject, @@ -95,17 +95,20 @@ export const PopoverTrigger = ( const { children, ...elementProps } = props; const clickProps = { - onClick: () => { + onClick: (event: PointerEvent) => { popover.setOpen(true); + elementProps.onClick?.(event); }, }; const hoverProps = { - onPointerEnter: () => { + onPointerEnter: (event: PointerEvent) => { popover.setOpen(true); + elementProps.onPointerEnter?.(event); }, - onPointerLeave: () => { + onPointerLeave: (event: PointerEvent) => { popover.setOpen(false); + elementProps.onPointerLeave?.(event); }, }; From 6adea6b60652a7691c749b2df0b3d970089ecefe Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 13 Sep 2024 19:47:04 +0000 Subject: [PATCH 07/17] wip: commit progress on FeatureBadge update --- .../FeatureBadge/FeatureBadge.stories.tsx | 23 +-- .../components/FeatureBadge/FeatureBadge.tsx | 134 ++++++++++++++++-- 2 files changed, 123 insertions(+), 34 deletions(-) diff --git a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx index 8b5236eb774a1..1d522ed83748c 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx @@ -12,28 +12,9 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const Small: Story = { +export const SmallInteractive: Story = { args: { size: "sm", - }, -}; - -export const Medium: Story = { - args: { - size: "md", - }, -}; - -export const HighlightedSmall: Story = { - args: { - size: "sm", - highlighted: true, - }, -}; - -export const HighlightedMedium: Story = { - args: { - size: "md", - highlighted: true, + variant: "interactive", }, }; diff --git a/site/src/components/FeatureBadge/FeatureBadge.tsx b/site/src/components/FeatureBadge/FeatureBadge.tsx index 0872a4b82faa4..ff1105cc8426f 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.tsx @@ -1,6 +1,16 @@ import type { Interpolation, Theme } from "@emotion/react"; +import Link from "@mui/material/Link"; import { visuallyHidden } from "@mui/utils"; -import type { FC, HTMLAttributes, ReactNode } from "react"; +import { HelpTooltipContent } from "components/HelpTooltip/HelpTooltip"; +import { Popover, PopoverTrigger } from "components/Popover/Popover"; +import { + useEffect, + useState, + type FC, + type HTMLAttributes, + type ReactNode, +} from "react"; +import { docs } from "utils/docs"; /** * All types of feature that we are currently supporting. Defined as record to @@ -8,6 +18,7 @@ import type { FC, HTMLAttributes, ReactNode } from "react"; */ const featureBadgeTypes = { beta: "beta", + experimental: "experimental", } as const satisfies Record; const styles = { @@ -16,11 +27,12 @@ const styles = { // 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 small badges will be the default fontSize: "0.75rem", - maxWidth: "fit-content", + cursor: "default", flexShrink: 0, padding: "4px 8px", lineHeight: 1, @@ -31,42 +43,138 @@ const styles = { borderRadius: "6px", }), - highlighted: (theme) => ({ + badgeHover: (theme) => ({ color: theme.palette.text.primary, borderColor: theme.palette.text.primary, }), - mediumText: { + badgeLargeText: { fontSize: "1rem", }, + + tooltipTitle: { + fontWeight: 600, + fontFamily: "inherit", + fontSize: 18, + margin: 0, + lineHeight: 1, + paddingBottom: "8px", + }, + + tooltipDescription: { + margin: 0, + lineHeight: 1.2, + }, } as const satisfies Record>; type FeatureBadgeProps = Readonly< Omit, "children"> & { type: keyof typeof featureBadgeTypes; - size?: "sm" | "md"; - highlighted?: boolean; + size?: "sm" | "lg"; + + /** + * Defines how the FeatureBadge should render. + * - interactive (default) - The badge functions like a link and + * controls its own hover styling. + * - static - The badge is completely static and has no interaction + * behavior. + * - staticHover - The badge is completely static, but displays badge + hover styling (but nothing related to links). Useful if you want a + parent component to control the hover styling. + */ + variant?: "interactive" | "static" | "staticHover"; } >; export const FeatureBadge: FC = ({ type, size = "sm", - highlighted = false, + variant = "interactive", + onPointerEnter, + onPointerLeave, ...delegatedProps }) => { - return ( + const [isBadgeHovering, setIsBadgeHovering] = useState(false); + useEffect(() => { + const onWindowBlur = () => { + setIsBadgeHovering(false); + }; + + window.addEventListener("blur", onWindowBlur); + return () => window.removeEventListener("blur", onWindowBlur); + }, []); + + const featureType = featureBadgeTypes[type]; + const showHoverStyles = + variant === "staticHover" || (variant === "interactive" && isBadgeHovering); + + const coreContent = ( - (This feature is - {featureBadgeTypes[type]} - ) + (This is a + {featureType} + feature) ); + + if (variant !== "interactive") { + return coreContent; + } + + return ( + + { + setIsBadgeHovering(true); + onPointerEnter?.(event); + }} + onPointerLeave={(event) => { + setIsBadgeHovering(false); + onPointerLeave?.(event); + }} + > + {coreContent} + + + +
+ {capitalizeFirstLetter(featureType)} Feature +
+ +

+ This is {getGrammaticalArticle(featureType)} {featureType} feature. It + has not yet been marked for general availability. +

+ + + Feature stage documentation + (link opens in new tab) + +
+
+ ); }; + +function getGrammaticalArticle(nextWord: string): string { + const vowels = ["a", "e", "i", "o", "u"]; + const firstLetter = nextWord.slice(0, 1).toLowerCase(); + return vowels.includes(firstLetter) ? "an" : "a"; +} + +function capitalizeFirstLetter(text: string): string { + return text.slice(0, 1).toUpperCase() + text.slice(1); +} From d4455a89f91d88cb6017ab11a9fb962d7d1dd33d Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 13 Sep 2024 20:05:09 +0000 Subject: [PATCH 08/17] wip: commit more progress --- .../FeatureBadge/FeatureBadge.stories.tsx | 11 ++++++++++- site/src/components/FeatureBadge/FeatureBadge.tsx | 15 +++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx index 1d522ed83748c..a4610a160802d 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx @@ -12,8 +12,17 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const SmallInteractive: Story = { +export const SmallInteractiveBeta: Story = { args: { + type: "beta", + size: "sm", + variant: "interactive", + }, +}; + +export const SmallInteractiveExperimental: Story = { + args: { + type: "experimental", size: "sm", variant: "interactive", }, diff --git a/site/src/components/FeatureBadge/FeatureBadge.tsx b/site/src/components/FeatureBadge/FeatureBadge.tsx index ff1105cc8426f..3ec1267a764ff 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.tsx @@ -52,17 +52,24 @@ const styles = { fontSize: "1rem", }, - tooltipTitle: { + 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>; @@ -159,9 +166,9 @@ export const FeatureBadge: FC = ({ href={docs("/contributing/feature-stages")} target="_blank" rel="noreferrer" - css={{ fontWeight: 600 }} + css={styles.tooltipLink} > - Feature stage documentation + Learn about feature stages (link opens in new tab) From 8ae71d389e2040435cab04c7d881ff61e51cbcac Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 13 Sep 2024 20:17:04 +0000 Subject: [PATCH 09/17] chore: update tag definitions to satify Biome --- .../AppearancePage/AppearanceForm.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/site/src/pages/UserSettingsPage/AppearancePage/AppearanceForm.tsx b/site/src/pages/UserSettingsPage/AppearancePage/AppearanceForm.tsx index a4e520c2b7137..6559e31723156 100644 --- a/site/src/pages/UserSettingsPage/AppearancePage/AppearanceForm.tsx +++ b/site/src/pages/UserSettingsPage/AppearancePage/AppearanceForm.tsx @@ -175,23 +175,23 @@ const ThemePreview: FC = ({
-
-
-
+
+
+
-
-
+
+
-
+
-
-
-
-
-
+
+
+
+
+
From 0ad68afb99fc5bcf2d1708637b7e1dd22fd30f8c Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 13 Sep 2024 21:00:40 +0000 Subject: [PATCH 10/17] chore: update all colors for preview role --- .../components/FeatureBadge/FeatureBadge.tsx | 14 +++++--- site/src/theme/dark/roles.ts | 36 +++++++++++++++---- site/src/theme/darkBlue/roles.ts | 36 +++++++++++++++---- site/src/theme/index.ts | 1 - site/src/theme/light/roles.ts | 36 +++++++++++++++---- site/src/theme/roles.ts | 2 +- 6 files changed, 97 insertions(+), 28 deletions(-) diff --git a/site/src/components/FeatureBadge/FeatureBadge.tsx b/site/src/components/FeatureBadge/FeatureBadge.tsx index 3ec1267a764ff..353a44b527962 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.tsx @@ -10,6 +10,7 @@ import { type HTMLAttributes, type ReactNode, } from "react"; +import tailwindColors from "theme/tailwindColors"; import { docs } from "utils/docs"; /** @@ -37,15 +38,18 @@ const styles = { padding: "4px 8px", lineHeight: 1, whiteSpace: "nowrap", - border: `1px solid ${theme.palette.divider}`, - color: theme.palette.text.secondary, - backgroundColor: theme.palette.background.default, + border: `1px solid ${theme.roles.preview.outline}`, + color: theme.roles.preview.text, + backgroundColor: theme.roles.preview.background, borderRadius: "6px", + transition: + "color 0.4s ease-in-out, border-color 0.4s ease-in-out, background-color 0.4s ease-in-out", }), badgeHover: (theme) => ({ - color: theme.palette.text.primary, - borderColor: theme.palette.text.primary, + color: theme.roles.preview.hover.text, + borderColor: theme.roles.preview.hover.outline, + backgroundColor: theme.roles.preview.hover.background, }), badgeLargeText: { diff --git a/site/src/theme/dark/roles.ts b/site/src/theme/dark/roles.ts index 32a9ea4f12992..dfefd1d10909b 100644 --- a/site/src/theme/dark/roles.ts +++ b/site/src/theme/dark/roles.ts @@ -1,7 +1,7 @@ import type { Roles } from "../roles"; import colors from "../tailwindColors"; -export default { +const roles: Roles = { danger: { background: colors.orange[950], outline: colors.orange[500], @@ -143,13 +143,35 @@ export default { }, }, preview: { - background: colors.violet[950], - outline: colors.violet[500], - text: colors.violet[50], + background: colors.cyan[950], + outline: colors.cyan[600], + text: colors.cyan[400], fill: { - solid: colors.violet[400], - outline: colors.violet[400], + solid: colors.cyan[400], + outline: colors.cyan[400], text: colors.white, }, + hover: { + background: colors.zinc[950], + outline: colors.cyan[500], + text: colors.cyan[300], + fill: { + text: colors.white, + outline: colors.cyan[600], + solid: colors.cyan[600], + }, + }, + disabled: { + background: colors.zinc[950], + outline: colors.cyan[500], + text: colors.cyan[300], + fill: { + text: colors.white, + outline: colors.cyan[600], + solid: colors.cyan[600], + }, + }, }, -} satisfies Roles; +}; + +export default roles; diff --git a/site/src/theme/darkBlue/roles.ts b/site/src/theme/darkBlue/roles.ts index 744b7329249b9..a0f36daa810b9 100644 --- a/site/src/theme/darkBlue/roles.ts +++ b/site/src/theme/darkBlue/roles.ts @@ -1,7 +1,7 @@ import type { Roles } from "../roles"; import colors from "../tailwindColors"; -export default { +const roles: Roles = { danger: { background: colors.orange[950], outline: colors.orange[500], @@ -143,13 +143,35 @@ export default { }, }, preview: { - background: colors.violet[950], - outline: colors.violet[500], - text: colors.violet[50], + background: colors.cyan[950], + outline: colors.cyan[600], + text: colors.cyan[400], fill: { - solid: colors.violet[400], - outline: colors.violet[400], + solid: colors.cyan[400], + outline: colors.cyan[400], text: colors.white, }, + hover: { + background: colors.zinc[950], + outline: colors.cyan[500], + text: colors.cyan[300], + fill: { + text: colors.white, + outline: colors.cyan[600], + solid: colors.cyan[600], + }, + }, + disabled: { + background: colors.zinc[950], + outline: colors.cyan[500], + text: colors.cyan[300], + fill: { + text: colors.white, + outline: colors.cyan[600], + solid: colors.cyan[600], + }, + }, }, -} satisfies Roles; +}; + +export default roles; diff --git a/site/src/theme/index.ts b/site/src/theme/index.ts index 8509ccb4135a6..7516fe1207929 100644 --- a/site/src/theme/index.ts +++ b/site/src/theme/index.ts @@ -1,4 +1,3 @@ -// biome-ignore lint/nursery/noRestrictedImports: We still use `Theme` as a basis for our actual theme, for now. import type { Theme as MuiTheme } from "@mui/material/styles"; import type * as monaco from "monaco-editor"; import type { Branding } from "./branding"; diff --git a/site/src/theme/light/roles.ts b/site/src/theme/light/roles.ts index fe3d1d9687bfa..905d45384f247 100644 --- a/site/src/theme/light/roles.ts +++ b/site/src/theme/light/roles.ts @@ -1,7 +1,7 @@ import type { Roles } from "../roles"; import colors from "../tailwindColors"; -export default { +const roles: Roles = { danger: { background: colors.orange[50], outline: colors.orange[400], @@ -143,13 +143,35 @@ export default { }, }, preview: { - background: colors.violet[50], - outline: colors.violet[500], - text: colors.violet[950], + background: colors.cyan[100], + outline: colors.cyan[500], + text: colors.cyan[950], fill: { - solid: colors.violet[600], - outline: colors.violet[600], + solid: colors.cyan[600], + outline: colors.cyan[600], text: colors.white, }, + hover: { + background: colors.cyan[50], + outline: colors.cyan[600], + text: colors.cyan[950], + fill: { + outline: colors.cyan[500], + solid: colors.cyan[500], + text: colors.white, + }, + }, + disabled: { + background: colors.cyan[50], + outline: colors.cyan[800], + text: colors.cyan[200], + fill: { + solid: colors.cyan[800], + outline: colors.cyan[800], + text: colors.white, + }, + }, }, -} satisfies Roles; +}; + +export default roles; diff --git a/site/src/theme/roles.ts b/site/src/theme/roles.ts index 13d54f8840d20..87620bd43ef8c 100644 --- a/site/src/theme/roles.ts +++ b/site/src/theme/roles.ts @@ -36,7 +36,7 @@ export interface Roles { /** This isn't quite ready for prime-time, but you're welcome to look around! * Preview features, experiments, unstable, etc. */ - preview: Role; + preview: InteractiveRole; } /** From 781a6099b81089b5dc1e3d3b6332d0177145db79 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 13 Sep 2024 21:45:02 +0000 Subject: [PATCH 11/17] fix: make sure badge shows as hovered while inside tooltip --- .../FeatureBadge/FeatureBadge.stories.tsx | 16 +++++++++++++++ .../components/FeatureBadge/FeatureBadge.tsx | 20 ++++++++++++++----- site/src/components/Popover/Popover.tsx | 17 ++++++++++++---- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx index a4610a160802d..7326015b23dae 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx @@ -27,3 +27,19 @@ export const SmallInteractiveExperimental: Story = { variant: "interactive", }, }; + +export const LargeInteractiveBeta: Story = { + args: { + type: "beta", + size: "lg", + variant: "interactive", + }, +}; + +export const LargeStaticBeta: Story = { + args: { + type: "beta", + size: "lg", + variant: "static", + }, +}; diff --git a/site/src/components/FeatureBadge/FeatureBadge.tsx b/site/src/components/FeatureBadge/FeatureBadge.tsx index 353a44b527962..57ba3e05b754e 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.tsx @@ -43,7 +43,7 @@ const styles = { backgroundColor: theme.roles.preview.background, borderRadius: "6px", transition: - "color 0.4s ease-in-out, border-color 0.4s ease-in-out, background-color 0.4s ease-in-out", + "color 0.2s ease-in-out, border-color 0.2s ease-in-out, background-color 0.2s ease-in-out", }), badgeHover: (theme) => ({ @@ -105,10 +105,15 @@ export const FeatureBadge: FC = ({ onPointerLeave, ...delegatedProps }) => { + // Not a big fan of having two hover variables, but we need to make sure the + // badge maintains its hover styling while the mouse is inside the tooltip const [isBadgeHovering, setIsBadgeHovering] = useState(false); + const [isTooltipHovering, setIsTooltipHovering] = useState(false); + useEffect(() => { const onWindowBlur = () => { setIsBadgeHovering(false); + setIsTooltipHovering(false); }; window.addEventListener("blur", onWindowBlur); @@ -116,16 +121,19 @@ export const FeatureBadge: FC = ({ }, []); const featureType = featureBadgeTypes[type]; - const showHoverStyles = - variant === "staticHover" || (variant === "interactive" && isBadgeHovering); + const showBadgeHoverStyle = + variant === "staticHover" || + (variant === "interactive" && (isBadgeHovering || isTooltipHovering)); const coreContent = ( (This is a @@ -156,6 +164,8 @@ export const FeatureBadge: FC = ({ setIsTooltipHovering(true)} + onPointerLeave={() => setIsTooltipHovering(false)} >
{capitalizeFirstLetter(featureType)} Feature @@ -163,7 +173,7 @@ export const FeatureBadge: FC = ({

This is {getGrammaticalArticle(featureType)} {featureType} feature. It - has not yet been marked for general availability. + has not yet reached generally availability (GA).

= ({ horizontal = "left", + onPointerEnter, + onPointerLeave, ...popoverProps }) => { const popover = usePopover(); @@ -155,7 +158,7 @@ export const PopoverContent: FC = ({ }, }} {...horizontalProps(horizontal)} - {...modeProps(popover)} + {...modeProps(popover, onPointerEnter, onPointerLeave)} {...popoverProps} id={popover.id} open={popover.open} @@ -165,14 +168,20 @@ export const PopoverContent: FC = ({ ); }; -const modeProps = (popover: PopoverContextValue) => { +const modeProps = ( + popover: PopoverContextValue, + externalOnPointerEnter: PointerEventHandler | undefined, + externalOnPointerLeave: PointerEventHandler | undefined, +) => { if (popover.mode === "hover") { return { - onPointerEnter: () => { + onPointerEnter: (event: PointerEvent) => { popover.setOpen(true); + externalOnPointerEnter?.(event); }, - onPointerLeave: () => { + onPointerLeave: (event: PointerEvent) => { popover.setOpen(false); + externalOnPointerLeave?.(event); }, }; } From a98186479dec68ecd4e1a1dc0d25d3d6e955610d Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 13 Sep 2024 22:10:25 +0000 Subject: [PATCH 12/17] wip: commit progress on adding story for controlled variant --- .../FeatureBadge/FeatureBadge.stories.tsx | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx index 7326015b23dae..842dbebd0353d 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx @@ -1,5 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { FeatureBadge } from "./FeatureBadge"; +import { useState } from "react"; +import { useTheme } from "@emotion/react"; const meta: Meta = { title: "components/FeatureBadge", @@ -43,3 +45,44 @@ export const LargeStaticBeta: Story = { variant: "static", }, }; + +export const HoverControlledByParent: Story = { + args: { + type: "experimental", + size: "sm", + }, + + decorators: (Story, context) => { + const theme = useTheme(); + const [isHovering, setIsHovering] = useState(false); + + return ( + + ); + }, +}; From f47d0598d7ee1ccb4ce5416a4ab2b1939595a5a3 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 16 Sep 2024 13:37:54 +0000 Subject: [PATCH 13/17] fix: sort imports --- site/src/components/FeatureBadge/FeatureBadge.stories.tsx | 4 ++-- site/src/components/FeatureBadge/FeatureBadge.tsx | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx index 842dbebd0353d..9143c130d809b 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx @@ -1,7 +1,7 @@ +import { useTheme } from "@emotion/react"; import type { Meta, StoryObj } from "@storybook/react"; -import { FeatureBadge } from "./FeatureBadge"; import { useState } from "react"; -import { useTheme } from "@emotion/react"; +import { FeatureBadge } from "./FeatureBadge"; const meta: Meta = { title: "components/FeatureBadge", diff --git a/site/src/components/FeatureBadge/FeatureBadge.tsx b/site/src/components/FeatureBadge/FeatureBadge.tsx index 57ba3e05b754e..eeb160e622118 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.tsx @@ -10,7 +10,6 @@ import { type HTMLAttributes, type ReactNode, } from "react"; -import tailwindColors from "theme/tailwindColors"; import { docs } from "utils/docs"; /** From ad61763536431103318b6a1295392e3f9c8c2660 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 16 Sep 2024 13:50:20 +0000 Subject: [PATCH 14/17] refactor: change component API to be more obvious/ergonomic --- .../FeatureBadge/FeatureBadge.stories.tsx | 3 +- .../components/FeatureBadge/FeatureBadge.tsx | 54 ++++++++++--------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx index 9143c130d809b..714b461ea4140 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.stories.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.stories.tsx @@ -79,7 +79,8 @@ export const HoverControlledByParent: Story = { {Story({ args: { ...context.initialArgs, - variant: isHovering ? "staticHover" : "static", + variant: "static", + highlighted: isHovering, }, })} diff --git a/site/src/components/FeatureBadge/FeatureBadge.tsx b/site/src/components/FeatureBadge/FeatureBadge.tsx index eeb160e622118..208f6f1c9db22 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.tsx @@ -77,29 +77,43 @@ const styles = { }, } as const satisfies Record>; +function grammaticalArticle(nextWord: string): string { + const vowels = ["a", "e", "i", "o", "u"]; + const firstLetter = nextWord.slice(0, 1).toLowerCase(); + return vowels.includes(firstLetter) ? "an" : "a"; +} + +function capitalizeFirstLetter(text: string): string { + return text.slice(0, 1).toUpperCase() + text.slice(1); +} + type FeatureBadgeProps = Readonly< Omit, "children"> & { type: keyof typeof featureBadgeTypes; size?: "sm" | "lg"; - - /** - * Defines how the FeatureBadge should render. - * - interactive (default) - The badge functions like a link and - * controls its own hover styling. - * - static - The badge is completely static and has no interaction - * behavior. - * - staticHover - The badge is completely static, but displays badge - hover styling (but nothing related to links). Useful if you want a - parent component to control the hover styling. - */ - variant?: "interactive" | "static" | "staticHover"; - } + } & ( + | { + /** + * Defines whether the FeatureBadge should act as a + * controlled or uncontrolled component with its hover and + * general interaction styling. + */ + variant: "interactive"; + + // Had to specify the highlighted key for this union option + // even though it won't be used, because otherwise the type + // ergonomics for users would be too clunky. + highlighted?: undefined; + } + | { variant: "static"; highlighted?: boolean } + ) >; export const FeatureBadge: FC = ({ type, size = "sm", variant = "interactive", + highlighted = false, onPointerEnter, onPointerLeave, ...delegatedProps @@ -121,7 +135,7 @@ export const FeatureBadge: FC = ({ const featureType = featureBadgeTypes[type]; const showBadgeHoverStyle = - variant === "staticHover" || + highlighted || (variant === "interactive" && (isBadgeHovering || isTooltipHovering)); const coreContent = ( @@ -171,7 +185,7 @@ export const FeatureBadge: FC = ({

- This is {getGrammaticalArticle(featureType)} {featureType} feature. It + This is {grammaticalArticle(featureType)} {featureType} feature. It has not yet reached generally availability (GA).

@@ -188,13 +202,3 @@ export const FeatureBadge: FC = ({ ); }; - -function getGrammaticalArticle(nextWord: string): string { - const vowels = ["a", "e", "i", "o", "u"]; - const firstLetter = nextWord.slice(0, 1).toLowerCase(); - return vowels.includes(firstLetter) ? "an" : "a"; -} - -function capitalizeFirstLetter(text: string): string { - return text.slice(0, 1).toUpperCase() + text.slice(1); -} From 6e16aaabb0f2a8a9650450d6d81c75e87f8deabd Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 16 Sep 2024 13:54:37 +0000 Subject: [PATCH 15/17] fix: add biome-ignore comments to more base files --- site/src/components/Popover/Popover.tsx | 1 + site/src/theme/index.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/site/src/components/Popover/Popover.tsx b/site/src/components/Popover/Popover.tsx index 0e12728667537..8b4479d95b3a4 100644 --- a/site/src/components/Popover/Popover.tsx +++ b/site/src/components/Popover/Popover.tsx @@ -1,5 +1,6 @@ import MuiPopover, { type PopoverProps as MuiPopoverProps, + // biome-ignore lint/nursery/noRestrictedImports: This is the base component that our custom popover is based on } from "@mui/material/Popover"; import { type FC, diff --git a/site/src/theme/index.ts b/site/src/theme/index.ts index 7516fe1207929..3079ce01b27a3 100644 --- a/site/src/theme/index.ts +++ b/site/src/theme/index.ts @@ -1,3 +1,4 @@ +// biome-ignore lint/nursery/noRestrictedImports: Have to use MUI styles as base import type { Theme as MuiTheme } from "@mui/material/styles"; import type * as monaco from "monaco-editor"; import type { Branding } from "./branding"; From 6a19b611ab02128674f9a460f2becfbd6aee0c7b Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 16 Sep 2024 13:59:54 +0000 Subject: [PATCH 16/17] fix: update import order again --- site/src/components/FeatureBadge/FeatureBadge.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/components/FeatureBadge/FeatureBadge.tsx b/site/src/components/FeatureBadge/FeatureBadge.tsx index 208f6f1c9db22..75b7c15045cde 100644 --- a/site/src/components/FeatureBadge/FeatureBadge.tsx +++ b/site/src/components/FeatureBadge/FeatureBadge.tsx @@ -4,11 +4,11 @@ import { visuallyHidden } from "@mui/utils"; import { HelpTooltipContent } from "components/HelpTooltip/HelpTooltip"; import { Popover, PopoverTrigger } from "components/Popover/Popover"; import { - useEffect, - useState, type FC, type HTMLAttributes, type ReactNode, + useEffect, + useState, } from "react"; import { docs } from "utils/docs"; From a2338673b592a6459c49373292d3568044b0c536 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 16 Sep 2024 14:03:30 +0000 Subject: [PATCH 17/17] chore: revert biome-ignore comment --- site/src/theme/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/theme/index.ts b/site/src/theme/index.ts index 3079ce01b27a3..8509ccb4135a6 100644 --- a/site/src/theme/index.ts +++ b/site/src/theme/index.ts @@ -1,4 +1,4 @@ -// biome-ignore lint/nursery/noRestrictedImports: Have to use MUI styles as base +// biome-ignore lint/nursery/noRestrictedImports: We still use `Theme` as a basis for our actual theme, for now. import type { Theme as MuiTheme } from "@mui/material/styles"; import type * as monaco from "monaco-editor"; import type { Branding } from "./branding";