Skip to content

Commit c2f018c

Browse files
authored
ui(performance): Make team transactions dropdown scrollable (#26159)
This makes the list of teams scrollable so it does not expand vertically indefinitely.
1 parent d51e509 commit c2f018c

File tree

3 files changed

+185
-82
lines changed

3 files changed

+185
-82
lines changed

static/app/components/performance/teamKeyTransaction.tsx

Lines changed: 123 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1-
import {Component, ReactElement} from 'react';
1+
import {Component, ComponentClass} from 'react';
22
import styled from '@emotion/styled';
33

44
import {toggleKeyTransaction} from 'app/actionCreators/performance';
55
import {Client} from 'app/api';
6+
import MenuHeader from 'app/components/actions/menuHeader';
67
import CheckboxFancy from 'app/components/checkboxFancy/checkboxFancy';
7-
import DropdownLink from 'app/components/dropdownLink';
8+
import DropdownControl, {Content} from 'app/components/dropdownControl';
9+
import {GetActorPropsFn} from 'app/components/dropdownMenu';
10+
import MenuItem from 'app/components/menuItem';
811
import {t} from 'app/locale';
912
import space from 'app/styles/space';
1013
import {Organization, Team} from 'app/types';
1114
import withApi from 'app/utils/withApi';
1215

13-
export type TitleProps = {
16+
export type TitleProps = Partial<ReturnType<GetActorPropsFn>> & {
1417
keyedTeamsCount: number;
1518
disabled?: boolean;
1619
};
@@ -21,7 +24,7 @@ type Props = {
2124
organization: Organization;
2225
teams: Team[];
2326
transactionName: string;
24-
title: (props: TitleProps) => ReactElement;
27+
title: ComponentClass<TitleProps>;
2528
};
2629

2730
type State = {
@@ -142,16 +145,17 @@ class TeamKeyTransaction extends Component<Props, State> {
142145
};
143146

144147
render() {
145-
const {teams, title: Title} = this.props;
148+
const {teams, title} = this.props;
146149
const {keyedTeams, isLoading} = this.state;
147150

148151
if (isLoading) {
149-
return <Title disabled keyedTeamsCount={keyedTeams.size} />;
152+
const Title = title;
153+
return <Title disabled keyedTeamsCount={0} />;
150154
}
151155

152156
return (
153157
<TeamKeyTransactionSelector
154-
title={<Title keyedTeamsCount={keyedTeams.size} />}
158+
title={title}
155159
handleToggleKeyTransaction={this.handleToggleKeyTransaction}
156160
teams={teams}
157161
keyedTeams={keyedTeams}
@@ -161,14 +165,14 @@ class TeamKeyTransaction extends Component<Props, State> {
161165
}
162166

163167
type SelectorProps = {
164-
title: React.ReactNode;
168+
title: ComponentClass<TitleProps>;
165169
handleToggleKeyTransaction: (selection: TeamSelection) => void;
166170
teams: Team[];
167171
keyedTeams: Set<string>;
168172
};
169173

170174
function TeamKeyTransactionSelector({
171-
title,
175+
title: Title,
172176
handleToggleKeyTransaction,
173177
teams,
174178
keyedTeams,
@@ -179,52 +183,129 @@ function TeamKeyTransactionSelector({
179183
};
180184

181185
return (
182-
<DropdownLink caret={false} title={title} anchorMiddle>
183-
<DropdownMenuHeader
184-
first
185-
onClick={toggleTeam({
186-
type: 'my teams',
187-
action: teams.length === keyedTeams.size ? 'unkey' : 'key',
188-
})}
189-
>
190-
{t('My Teams')}
191-
<StyledCheckbox
192-
isChecked={teams.length === keyedTeams.size}
193-
isIndeterminate={teams.length > keyedTeams.size && keyedTeams.size > 0}
194-
/>
195-
</DropdownMenuHeader>
196-
{teams.map(team => (
197-
<DropdownMenuItem
198-
key={team.slug}
199-
onClick={toggleTeam({
200-
type: 'id',
201-
action: keyedTeams.has(team.id) ? 'unkey' : 'key',
202-
teamId: team.id,
203-
})}
186+
<DropdownControl
187+
button={({getActorProps}) => (
188+
<Title keyedTeamsCount={keyedTeams.size} {...getActorProps()} />
189+
)}
190+
>
191+
{({isOpen, getMenuProps}) => (
192+
<DropdownWrapper
193+
{...getMenuProps()}
194+
isOpen={isOpen}
195+
blendCorner
196+
alignMenu="right"
197+
width="220px"
204198
>
205-
{team.name}
206-
<StyledCheckbox isChecked={keyedTeams.has(team.id)} />
207-
</DropdownMenuItem>
208-
))}
209-
</DropdownLink>
199+
{isOpen && (
200+
<DropdownContent>
201+
<DropdownMenuHeader first>
202+
{t('My Teams')}
203+
<StyledCheckbox
204+
isChecked={teams.length === keyedTeams.size}
205+
isIndeterminate={teams.length > keyedTeams.size && keyedTeams.size > 0}
206+
onClick={toggleTeam({
207+
type: 'my teams',
208+
action: teams.length === keyedTeams.size ? 'unkey' : 'key',
209+
})}
210+
/>
211+
</DropdownMenuHeader>
212+
{teams.map(team => (
213+
<DropdownMenuItem
214+
key={team.slug}
215+
onClick={toggleTeam({
216+
type: 'id',
217+
action: keyedTeams.has(team.id) ? 'unkey' : 'key',
218+
teamId: team.id,
219+
})}
220+
>
221+
<MenuItemContent>
222+
{team.name}
223+
<StyledCheckbox isChecked={keyedTeams.has(team.id)} />
224+
</MenuItemContent>
225+
</DropdownMenuItem>
226+
))}
227+
</DropdownContent>
228+
)}
229+
</DropdownWrapper>
230+
)}
231+
</DropdownControl>
210232
);
211233
}
212234

213-
const DropdownMenuItemBase = styled('li')`
235+
const DropdownWrapper = styled(Content)`
236+
margin-top: 9px;
237+
left: auto;
238+
right: 50%;
239+
transform: translateX(calc(50%));
240+
241+
/* Adapted from the dropdown-menu class */
242+
border: none;
243+
border-radius: 2px;
244+
box-shadow: 0 0 0 1px rgba(52, 60, 69, 0.2), 0 1px 3px rgba(70, 82, 98, 0.25);
245+
background-clip: padding-box;
246+
overflow: visible;
247+
248+
&:before {
249+
width: 0;
250+
height: 0;
251+
border-left: 9px solid transparent;
252+
border-right: 9px solid transparent;
253+
border-bottom: 9px solid ${p => p.theme.border};
254+
content: '';
255+
display: block;
256+
position: absolute;
257+
top: -9px;
258+
left: calc(50% - 9px);
259+
right: auto;
260+
z-index: -2;
261+
}
262+
263+
&:after {
264+
width: 0;
265+
height: 0;
266+
border-left: 8px solid transparent;
267+
border-right: 8px solid transparent;
268+
border-bottom: 8px solid ${p => p.theme.background};
269+
content: '';
270+
display: block;
271+
position: absolute;
272+
top: -8px;
273+
left: calc(50% - 8px);
274+
right: auto;
275+
z-index: -1;
276+
}
277+
`;
278+
279+
const DropdownContent = styled('div')`
280+
max-height: 250px;
281+
overflow-y: auto;
282+
`;
283+
284+
const DropdownMenuHeader = styled(MenuHeader)<{first?: boolean}>`
214285
display: flex;
215286
flex-direction: row;
216287
justify-content: space-between;
217288
align-items: center;
218-
padding: ${space(1)} ${space(1.5)};
219-
`;
289+
padding: ${space(1.5)} ${space(2)};
220290
221-
const DropdownMenuHeader = styled(DropdownMenuItemBase)<{first?: boolean}>`
222291
background: ${p => p.theme.backgroundSecondary};
223292
${p => p.first && 'border-radius: 2px'};
224293
`;
225294

226-
const DropdownMenuItem = styled(DropdownMenuItemBase)`
227-
border-top: 1px solid ${p => p.theme.border};
295+
const DropdownMenuItem = styled(MenuItem)`
296+
font-size: ${p => p.theme.fontSizeMedium};
297+
298+
&:not(:last-child) {
299+
border-bottom: 1px solid ${p => p.theme.innerBorder};
300+
}
301+
`;
302+
303+
const MenuItemContent = styled('div')`
304+
display: flex;
305+
flex-direction: row;
306+
justify-content: space-between;
307+
align-items: center;
308+
width: 100%;
228309
`;
229310

230311
const StyledCheckbox = styled(CheckboxFancy)`

static/app/views/performance/transactionSummary/teamKeyTransactionButton.tsx

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {Component} from 'react';
12
import styled from '@emotion/styled';
23

34
import Button from 'app/components/button';
@@ -17,6 +18,26 @@ type Props = {
1718
transactionName: string;
1819
};
1920

21+
/**
22+
* This can't be a function component because `TeamKeyTransaction` uses
23+
* `DropdownControl` which in turn uses passes a ref to this component.
24+
*/
25+
class TitleButton extends Component<TitleProps> {
26+
render() {
27+
const {keyedTeamsCount, ...props} = this.props;
28+
return (
29+
<StyledButton
30+
{...props}
31+
icon={keyedTeamsCount ? <IconStar color="yellow300" isSolid /> : <IconStar />}
32+
>
33+
{keyedTeamsCount
34+
? tn('Starred for Team', 'Starred for Teams', keyedTeamsCount)
35+
: t('Star for Team')}
36+
</StyledButton>
37+
);
38+
}
39+
}
40+
2041
function TeamKeyTransactionButton({eventView, teams, ...props}: Props) {
2142
if (eventView.project.length !== 1) {
2243
return <TitleButton disabled keyedTeamsCount={0} />;
@@ -33,19 +54,6 @@ function TeamKeyTransactionButton({eventView, teams, ...props}: Props) {
3354
);
3455
}
3556

36-
function TitleButton({disabled, keyedTeamsCount}: TitleProps) {
37-
return (
38-
<StyledButton
39-
disabled={disabled}
40-
icon={keyedTeamsCount ? <IconStar color="yellow300" isSolid /> : <IconStar />}
41-
>
42-
{keyedTeamsCount
43-
? tn('Starred for Team', 'Starred for Teams', keyedTeamsCount)
44-
: t('Star for Team')}
45-
</StyledButton>
46-
);
47-
}
48-
4957
const StyledButton = styled(Button)`
5058
width: 180px;
5159
`;

0 commit comments

Comments
 (0)