Skip to content

Commit fb86964

Browse files
committed
refactor: redefine FeatureStageBadge
1 parent 0197466 commit fb86964

File tree

9 files changed

+61
-106
lines changed

9 files changed

+61
-106
lines changed

site/src/components/FeatureStageBadge/FeatureStageBadge.tsx

Lines changed: 23 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,7 @@ import Link from "@mui/material/Link";
33
import { visuallyHidden } from "@mui/utils";
44
import { HelpTooltipContent } from "components/HelpTooltip/HelpTooltip";
55
import { Popover, PopoverTrigger } from "components/Popover/Popover";
6-
import {
7-
type FC,
8-
type HTMLAttributes,
9-
type ReactNode,
10-
useEffect,
11-
useState,
12-
} from "react";
6+
import type { FC, HTMLAttributes, ReactNode } from "react";
137
import { docs } from "utils/docs";
148

159
/**
@@ -29,7 +23,7 @@ const styles = {
2923
display: "block",
3024
maxWidth: "fit-content",
3125

32-
// Base style assumes that small badges will be the default
26+
// Base style assumes that medium badges will be the default
3327
fontSize: "0.75rem",
3428

3529
cursor: "default",
@@ -55,7 +49,7 @@ const styles = {
5549
fontSize: "1rem",
5650
},
5751

58-
badgeExtraSmallText: {
52+
badgeSmallText: {
5953
// Have to beef up font weight so that the letters still maintain the
6054
// same relative thickness as all our other main UI text
6155
fontWeight: 500,
@@ -86,83 +80,39 @@ const styles = {
8680

8781
type FeatureStageBadgeProps = Readonly<
8882
Omit<HTMLAttributes<HTMLSpanElement>, "children"> & {
89-
type: keyof typeof featureStageBadgeTypes;
90-
size?: "xs" | "sm" | "lg";
91-
variant: "interactive" | "static";
83+
contentType: keyof typeof featureStageBadgeTypes;
84+
size?: "sm" | "md" | "lg";
9285
}
9386
>;
9487

9588
export const FeatureStageBadge: FC<FeatureStageBadgeProps> = ({
96-
type,
97-
size = "sm",
98-
variant = "interactive",
99-
onPointerEnter,
100-
onPointerLeave,
89+
contentType,
90+
size = "md",
10191
...delegatedProps
10292
}) => {
103-
// Not a big fan of having two hover variables, but we need to make sure the
104-
// badge maintains its hover styling while the mouse is inside the tooltip.
105-
// If we had one variable, we could have race conditions based on how events
106-
// decide to bubble (especially with custom event handlers)
107-
const [isBadgeHovering, setIsBadgeHovering] = useState(false);
108-
const [isTooltipHovering, setIsTooltipHovering] = useState(false);
109-
110-
useEffect(() => {
111-
const onWindowBlur = () => {
112-
setIsBadgeHovering(false);
113-
setIsTooltipHovering(false);
114-
};
115-
116-
window.addEventListener("blur", onWindowBlur);
117-
return () => window.removeEventListener("blur", onWindowBlur);
118-
}, []);
119-
120-
const featureType = featureStageBadgeTypes[type];
121-
const showBadgeHoverStyle =
122-
variant === "interactive" && (isBadgeHovering || isTooltipHovering);
123-
124-
const coreContent = (
125-
<span
126-
css={[
127-
styles.badge,
128-
size === "xs" && styles.badgeExtraSmallText,
129-
size === "lg" && styles.badgeLargeText,
130-
showBadgeHoverStyle && styles.badgeHover,
131-
]}
132-
onPointerEnter={variant === "interactive" ? undefined : onPointerEnter}
133-
onPointerLeave={variant === "interactive" ? undefined : onPointerLeave}
134-
{...delegatedProps}
135-
>
136-
<span style={visuallyHidden}> (This is a</span>
137-
{featureType}
138-
<span style={visuallyHidden}> feature)</span>
139-
</span>
140-
);
141-
142-
if (variant !== "interactive") {
143-
return coreContent;
144-
}
145-
14693
return (
14794
<Popover mode="hover">
148-
<PopoverTrigger
149-
onPointerEnter={(event) => {
150-
setIsBadgeHovering(true);
151-
onPointerEnter?.(event);
152-
}}
153-
onPointerLeave={(event) => {
154-
setIsBadgeHovering(false);
155-
onPointerLeave?.(event);
156-
}}
157-
>
158-
{coreContent}
95+
<PopoverTrigger>
96+
{({ isOpen }) => (
97+
<span
98+
css={[
99+
styles.badge,
100+
size === "sm" && styles.badgeSmallText,
101+
size === "lg" && styles.badgeLargeText,
102+
isOpen && styles.badgeHover,
103+
]}
104+
{...delegatedProps}
105+
>
106+
<span style={visuallyHidden}> (This is a</span>
107+
{featureStageBadgeTypes[contentType]}
108+
<span style={visuallyHidden}> feature)</span>
109+
</span>
110+
)}
159111
</PopoverTrigger>
160112

161113
<HelpTooltipContent
162114
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
163115
transformOrigin={{ vertical: "top", horizontal: "center" }}
164-
onPointerEnter={() => setIsTooltipHovering(true)}
165-
onPointerLeave={() => setIsTooltipHovering(false)}
166116
>
167117
<p css={styles.tooltipDescription}>
168118
This feature has not yet reached general availability (GA).

site/src/components/Popover/Popover.tsx

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ type TriggerMode = "hover" | "click";
2222

2323
type TriggerRef = RefObject<HTMLElement>;
2424

25-
type TriggerElement = ReactElement<{
26-
ref: TriggerRef;
27-
onClick?: () => void;
28-
}>;
25+
// Have to append ReactNode type to satisfy React's cloneElement function. It
26+
// has absolutely no bearing on what happens at runtime
27+
type TriggerElement = ReactNode &
28+
ReactElement<{
29+
ref: TriggerRef;
30+
onClick?: () => void;
31+
}>;
2932

3033
type PopoverContextValue = {
3134
id: string;
@@ -88,33 +91,47 @@ export const usePopover = () => {
8891
return context;
8992
};
9093

91-
export const PopoverTrigger = (
92-
props: HTMLAttributes<HTMLElement> & {
93-
children: TriggerElement;
94-
},
95-
) => {
94+
type PopoverTriggerRenderProps = Readonly<{
95+
isOpen: boolean;
96+
}>;
97+
98+
type PopoverTriggerProps = Readonly<
99+
Omit<HTMLAttributes<HTMLElement>, "children"> & {
100+
children:
101+
| TriggerElement
102+
| ((props: PopoverTriggerRenderProps) => TriggerElement);
103+
}
104+
>;
105+
106+
export const PopoverTrigger: FC<PopoverTriggerProps> = (props) => {
96107
const popover = usePopover();
97-
const { children, ...elementProps } = props;
108+
const { children, onClick, onPointerEnter, onPointerLeave, ...elementProps } =
109+
props;
98110

99111
const clickProps = {
100112
onClick: (event: PointerEvent<HTMLElement>) => {
101113
popover.setOpen(true);
102-
elementProps.onClick?.(event);
114+
onClick?.(event);
103115
},
104116
};
105117

106118
const hoverProps = {
107119
onPointerEnter: (event: PointerEvent<HTMLElement>) => {
108120
popover.setOpen(true);
109-
elementProps.onPointerEnter?.(event);
121+
onPointerEnter?.(event);
110122
},
111123
onPointerLeave: (event: PointerEvent<HTMLElement>) => {
112124
popover.setOpen(false);
113-
elementProps.onPointerLeave?.(event);
125+
onPointerLeave?.(event);
114126
},
115127
};
116128

117-
return cloneElement(props.children, {
129+
const evaluatedChildren =
130+
typeof children === "function"
131+
? children({ isOpen: popover.open })
132+
: children;
133+
134+
return cloneElement(evaluatedChildren, {
118135
...elementProps,
119136
...(popover.mode === "click" ? clickProps : hoverProps),
120137
"aria-haspopup": true,

site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPage.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,7 @@ export const CustomRolesPage: FC = () => {
6767
<SettingsHeader
6868
title="Custom Roles"
6969
description="Manage custom roles for this organization."
70-
badges={
71-
<FeatureStageBadge type="beta" variant="interactive" size="lg" />
72-
}
70+
badges={<FeatureStageBadge contentType="beta" size="lg" />}
7371
/>
7472
{permissions.assignOrgRole && isCustomRolesEnabled && (
7573
<Button component={RouterLink} startIcon={<AddIcon />} to="create">

site/src/pages/ManagementSettingsPage/GroupsPage/GroupsPage.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,7 @@ export const GroupsPage: FC = () => {
8181
<SettingsHeader
8282
title="Groups"
8383
description="Manage groups for this organization."
84-
badges={
85-
<FeatureStageBadge type="beta" variant="interactive" size="lg" />
86-
}
84+
badges={<FeatureStageBadge contentType="beta" size="lg" />}
8785
/>
8886
{permissions.createGroup && feats.template_rbac && (
8987
<Button component={RouterLink} startIcon={<GroupAdd />} to="create">

site/src/pages/ManagementSettingsPage/IdpSyncPage/IdpSyncPage.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,7 @@ export const IdpSyncPage: FC = () => {
7474
title="IdP Sync"
7575
description="Group and role sync mappings (configured outside Coder)."
7676
tooltip={<IdpSyncHelpTooltip />}
77-
badges={
78-
<FeatureStageBadge type="beta" size="lg" variant="interactive" />
79-
}
77+
badges={<FeatureStageBadge contentType="beta" size="lg" />}
8078
/>
8179
<Stack direction="row" spacing={2}>
8280
<Button

site/src/pages/ManagementSettingsPage/OrganizationMembersPageView.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@ export const OrganizationMembersPageView: FC<
6363
<div>
6464
<SettingsHeader
6565
title="Members"
66-
badges={
67-
<FeatureStageBadge type="beta" size="lg" variant="interactive" />
68-
}
66+
badges={<FeatureStageBadge contentType="beta" size="lg" />}
6967
/>
7068

7169
<Stack>

site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ export const OrganizationProvisionersPageView: FC<
3232
>
3333
<SettingsHeader
3434
title="Provisioners"
35-
badges={
36-
<FeatureStageBadge type="beta" variant="interactive" size="lg" />
37-
}
35+
badges={<FeatureStageBadge contentType="beta" size="lg" />}
3836
/>
3937
<Button
4038
endIcon={<OpenInNewIcon />}

site/src/pages/ManagementSettingsPage/OrganizationSettingsPageView.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,7 @@ export const OrganizationSettingsPageView: FC<
6969
<div>
7070
<SettingsHeader
7171
title="Settings"
72-
badges={
73-
<FeatureStageBadge type="beta" variant="interactive" size="lg" />
74-
}
72+
badges={<FeatureStageBadge contentType="beta" size="lg" />}
7573
/>
7674

7775
{Boolean(error) && !isApiValidationError(error) && (

site/src/pages/ManagementSettingsPage/SidebarView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ const OrganizationsSettingsNavigation: FC<
203203
}}
204204
>
205205
<h2 css={styles.sidebarHeader}>Organizations</h2>
206-
<FeatureStageBadge type="beta" variant="interactive" size="sm" />
206+
<FeatureStageBadge contentType="beta" size="sm" />
207207
</header>
208208

209209
{permissions.createOrganization && (

0 commit comments

Comments
 (0)