Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit661d226

Browse files
authored
feat: create UI badges for labeling beta features (#14661)
* chore: finish draft work for FeatureBadge component* fix: add visually-hidden helper text for screen readers* chore: add stories for highlighted state* fix: update base styles* chore: remove debug display option* chore: update Popover to propagate events* wip: commit progress on FeatureBadge update* wip: commit more progress* chore: update tag definitions to satify Biome* chore: update all colors for preview role* fix: make sure badge shows as hovered while inside tooltip* wip: commit progress on adding story for controlled variant* fix: sort imports* refactor: change component API to be more obvious/ergonomic* fix: add biome-ignore comments to more base files* fix: update import order again* chore: revert biome-ignore comment* chore: update body text for tooltip* chore: update dark preivew role to use sky palette* chore: update color palettes for light/darkBlue themes* chore: add beta badge to organizations subheader* chore: add beta badge to organizations settings page* chore: beef up font weight for form header* fix: update text contrast for org menu list* chore: add beta badge to deployment dropdown* fix: run biome on imports* chore: remove API for controlling FeatureBadge hover styling externally* chore: add xs size for badge* fix: update font weight for xs feature badges* chore: add beta badges to all org headers* fix: turn badges and tooltips into separate concerns* fix: update hover styling* docs: update wording on comment* fix: apply formatting* chore: rename FeatureBadge to FeatureStageBadge* refactor: redefine FeatureStageBadge* chore: update stories* fix: add blur behavior to popover* chore: revert theme colors* chore: create featureStage branding namespace* fix: make sure cleanup function is set up properly* docs: update wording on comment for clarity* refactor: move styles down
1 parent3338f32 commit661d226

File tree

22 files changed

+417
-95
lines changed

22 files changed

+417
-95
lines changed

‎.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"stretchr",
121121
"STTY",
122122
"stuntest",
123+
"subpage",
123124
"tailbroker",
124125
"tailcfg",
125126
"tailexchange",
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
importtype{Meta,StoryObj}from"@storybook/react";
2+
import{FeatureStageBadge}from"./FeatureStageBadge";
3+
4+
constmeta:Meta<typeofFeatureStageBadge>={
5+
title:"components/FeatureStageBadge",
6+
component:FeatureStageBadge,
7+
args:{
8+
contentType:"beta",
9+
},
10+
};
11+
12+
exportdefaultmeta;
13+
typeStory=StoryObj<typeofFeatureStageBadge>;
14+
15+
exportconstMediumBeta:Story={
16+
args:{
17+
size:"md",
18+
},
19+
};
20+
21+
exportconstSmallBeta:Story={
22+
args:{
23+
size:"sm",
24+
},
25+
};
26+
27+
exportconstLargeBeta:Story={
28+
args:{
29+
size:"lg",
30+
},
31+
};
32+
33+
exportconstMediumExperimental:Story={
34+
args:{
35+
size:"md",
36+
contentType:"experimental",
37+
},
38+
};
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
importtype{Interpolation,Theme}from"@emotion/react";
2+
importLinkfrom"@mui/material/Link";
3+
import{visuallyHidden}from"@mui/utils";
4+
import{HelpTooltipContent}from"components/HelpTooltip/HelpTooltip";
5+
import{Popover,PopoverTrigger}from"components/Popover/Popover";
6+
importtype{FC,HTMLAttributes,ReactNode}from"react";
7+
import{docs}from"utils/docs";
8+
9+
/**
10+
* All types of feature that we are currently supporting. Defined as record to
11+
* ensure that we can't accidentally make typos when writing the badge text.
12+
*/
13+
constfeatureStageBadgeTypes={
14+
beta:"beta",
15+
experimental:"experimental",
16+
}asconstsatisfiesRecord<string,ReactNode>;
17+
18+
typeFeatureStageBadgeProps=Readonly<
19+
Omit<HTMLAttributes<HTMLSpanElement>,"children">&{
20+
contentType:keyoftypeoffeatureStageBadgeTypes;
21+
size?:"sm"|"md"|"lg";
22+
}
23+
>;
24+
25+
exportconstFeatureStageBadge:FC<FeatureStageBadgeProps>=({
26+
contentType,
27+
size="md",
28+
...delegatedProps
29+
})=>{
30+
return(
31+
<Popovermode="hover">
32+
<PopoverTrigger>
33+
{({ isOpen})=>(
34+
<span
35+
css={[
36+
styles.badge,
37+
size==="sm"&&styles.badgeSmallText,
38+
size==="lg"&&styles.badgeLargeText,
39+
isOpen&&styles.badgeHover,
40+
]}
41+
{...delegatedProps}
42+
>
43+
<spanstyle={visuallyHidden}> (This is a</span>
44+
{featureStageBadgeTypes[contentType]}
45+
<spanstyle={visuallyHidden}> feature)</span>
46+
</span>
47+
)}
48+
</PopoverTrigger>
49+
50+
<HelpTooltipContent
51+
anchorOrigin={{vertical:"bottom",horizontal:"center"}}
52+
transformOrigin={{vertical:"top",horizontal:"center"}}
53+
>
54+
<pcss={styles.tooltipDescription}>
55+
This feature has not yet reached general availability (GA).
56+
</p>
57+
58+
<Link
59+
href={docs("/contributing/feature-stages")}
60+
target="_blank"
61+
rel="noreferrer"
62+
css={styles.tooltipLink}
63+
>
64+
Learn about feature stages
65+
<spanstyle={visuallyHidden}> (link opens in new tab)</span>
66+
</Link>
67+
</HelpTooltipContent>
68+
</Popover>
69+
);
70+
};
71+
72+
conststyles={
73+
badge:(theme)=>({
74+
// Base type is based on a span so that the element can be placed inside
75+
// more types of HTML elements without creating invalid markdown, but we
76+
// still want the default display behavior to be div-like
77+
display:"block",
78+
maxWidth:"fit-content",
79+
80+
// Base style assumes that medium badges will be the default
81+
fontSize:"0.75rem",
82+
83+
cursor:"default",
84+
flexShrink:0,
85+
padding:"4px 8px",
86+
lineHeight:1,
87+
whiteSpace:"nowrap",
88+
border:`1px solid${theme.branding.featureStage.border}`,
89+
color:theme.branding.featureStage.text,
90+
backgroundColor:theme.branding.featureStage.background,
91+
borderRadius:"6px",
92+
transition:
93+
"color 0.2s ease-in-out, border-color 0.2s ease-in-out, background-color 0.2s ease-in-out",
94+
}),
95+
96+
badgeHover:(theme)=>({
97+
color:theme.branding.featureStage.hover.text,
98+
borderColor:theme.branding.featureStage.hover.border,
99+
backgroundColor:theme.branding.featureStage.hover.background,
100+
}),
101+
102+
badgeLargeText:{
103+
fontSize:"1rem",
104+
},
105+
106+
badgeSmallText:{
107+
// Have to beef up font weight so that the letters still maintain the
108+
// same relative thickness as all our other main UI text
109+
fontWeight:500,
110+
fontSize:"0.625rem",
111+
},
112+
113+
tooltipTitle:(theme)=>({
114+
color:theme.palette.text.primary,
115+
fontWeight:600,
116+
fontFamily:"inherit",
117+
fontSize:18,
118+
margin:0,
119+
lineHeight:1,
120+
paddingBottom:"8px",
121+
}),
122+
123+
tooltipDescription:{
124+
margin:0,
125+
lineHeight:1.4,
126+
paddingBottom:"8px",
127+
},
128+
129+
tooltipLink:{
130+
fontWeight:600,
131+
lineHeight:1.2,
132+
},
133+
}asconstsatisfiesRecord<string,Interpolation<Theme>>;

‎site/src/components/Form/Form.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ const styles = {
170170
formSectionInfoTitle:(theme)=>({
171171
fontSize:20,
172172
color:theme.palette.text.primary,
173-
fontWeight:400,
173+
fontWeight:500,
174174
margin:0,
175175
marginBottom:8,
176176
display:"flex",

‎site/src/components/Popover/Popover.tsx

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
importMuiPopover,{
22
typePopoverPropsasMuiPopoverProps,
3-
// biome-ignore lint/nursery/noRestrictedImports:Used asbase component
3+
// biome-ignore lint/nursery/noRestrictedImports:This is thebase component that our custom popover is based on
44
}from"@mui/material/Popover";
55
import{
66
typeFC,
77
typeHTMLAttributes,
8+
typePointerEvent,
9+
typePointerEventHandler,
810
typeReactElement,
911
typeReactNode,
1012
typeRefObject,
1113
cloneElement,
1214
createContext,
1315
useContext,
16+
useEffect,
1417
useId,
1518
useRef,
1619
useState,
@@ -20,10 +23,13 @@ type TriggerMode = "hover" | "click";
2023

2124
typeTriggerRef=RefObject<HTMLElement>;
2225

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

2834
typePopoverContextValue={
2935
id:string;
@@ -61,6 +67,15 @@ export const Popover: FC<PopoverProps> = (props) => {
6167
const[uncontrolledOpen,setUncontrolledOpen]=useState(false);
6268
consttriggerRef:TriggerRef=useRef(null);
6369

70+
// Helps makes sure that popovers close properly when the user switches to
71+
// a different tab. This won't help with controlled instances of the
72+
// component, but this is basically the most we can do from here
73+
useEffect(()=>{
74+
constcloseOnTabSwitch=()=>setUncontrolledOpen(false);
75+
window.addEventListener("blur",closeOnTabSwitch);
76+
return()=>window.removeEventListener("blur",closeOnTabSwitch);
77+
},[]);
78+
6479
constvalue:PopoverContextValue={
6580
triggerRef,
6681
id:`${hookId}-popover`,
@@ -86,30 +101,47 @@ export const usePopover = () => {
86101
returncontext;
87102
};
88103

89-
exportconstPopoverTrigger=(
90-
props:HTMLAttributes<HTMLElement>&{
91-
children:TriggerElement;
92-
},
93-
)=>{
104+
typePopoverTriggerRenderProps=Readonly<{
105+
isOpen:boolean;
106+
}>;
107+
108+
typePopoverTriggerProps=Readonly<
109+
Omit<HTMLAttributes<HTMLElement>,"children">&{
110+
children:
111+
|TriggerElement
112+
|((props:PopoverTriggerRenderProps)=>TriggerElement);
113+
}
114+
>;
115+
116+
exportconstPopoverTrigger:FC<PopoverTriggerProps>=(props)=>{
94117
constpopover=usePopover();
95-
const{ children, ...elementProps}=props;
118+
const{ children, onClick, onPointerEnter, onPointerLeave, ...elementProps}=
119+
props;
96120

97121
constclickProps={
98-
onClick:()=>{
122+
onClick:(event:PointerEvent<HTMLElement>)=>{
99123
popover.setOpen(true);
124+
onClick?.(event);
100125
},
101126
};
102127

103128
consthoverProps={
104-
onPointerEnter:()=>{
129+
onPointerEnter:(event:PointerEvent<HTMLElement>)=>{
105130
popover.setOpen(true);
131+
onPointerEnter?.(event);
106132
},
107-
onPointerLeave:()=>{
133+
onPointerLeave:(event:PointerEvent<HTMLElement>)=>{
108134
popover.setOpen(false);
135+
onPointerLeave?.(event);
109136
},
110137
};
111138

112-
returncloneElement(props.children,{
139+
constevaluatedChildren=
140+
typeofchildren==="function"
141+
?children({isOpen:popover.open})
142+
:children;
143+
144+
returncloneElement(evaluatedChildren,{
113145
...elementProps,
114146
...(popover.mode==="click" ?clickProps :hoverProps),
115147
"aria-haspopup":true,
@@ -130,6 +162,8 @@ export type PopoverContentProps = Omit<
130162

131163
exportconstPopoverContent:FC<PopoverContentProps>=({
132164
horizontal="left",
165+
onPointerEnter,
166+
onPointerLeave,
133167
...popoverProps
134168
})=>{
135169
constpopover=usePopover();
@@ -152,7 +186,7 @@ export const PopoverContent: FC<PopoverContentProps> = ({
152186
},
153187
}}
154188
{...horizontalProps(horizontal)}
155-
{...modeProps(popover)}
189+
{...modeProps(popover,onPointerEnter,onPointerLeave)}
156190
{...popoverProps}
157191
id={popover.id}
158192
open={popover.open}
@@ -162,14 +196,20 @@ export const PopoverContent: FC<PopoverContentProps> = ({
162196
);
163197
};
164198

165-
constmodeProps=(popover:PopoverContextValue)=>{
199+
constmodeProps=(
200+
popover:PopoverContextValue,
201+
externalOnPointerEnter:PointerEventHandler<HTMLDivElement>|undefined,
202+
externalOnPointerLeave:PointerEventHandler<HTMLDivElement>|undefined,
203+
)=>{
166204
if(popover.mode==="hover"){
167205
return{
168-
onPointerEnter:()=>{
206+
onPointerEnter:(event:PointerEvent<HTMLDivElement>)=>{
169207
popover.setOpen(true);
208+
externalOnPointerEnter?.(event);
170209
},
171-
onPointerLeave:()=>{
210+
onPointerLeave:(event:PointerEvent<HTMLDivElement>)=>{
172211
popover.setOpen(false);
212+
externalOnPointerLeave?.(event);
173213
},
174214
};
175215
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp