Skip to content

Commit 595b1ea

Browse files
JonasBaclaude
andauthored
ref(compactSelect) use MenuComponents for CompactSelect Slots (#109198)
Refactors usage of CompactSelect footer and title trailing props to mostly use the newly created MenuComponent prebuilt elements. This standardizes on the sizing, naming as well as some behavior of the selects so that they appear and act in a consistent manner throughout the UI Fix DE-713, DE-753, DE-863 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent e50a1d8 commit 595b1ea

File tree

22 files changed

+293
-468
lines changed

22 files changed

+293
-468
lines changed

static/app/components/assigneeSelectorDropdown.tsx

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import styled from '@emotion/styled';
33
import uniqBy from 'lodash/uniqBy';
44

55
import {ActorAvatar} from '@sentry/scraps/avatar';
6-
import {Button} from '@sentry/scraps/button';
76
import {
87
CompactSelect,
8+
MenuComponents,
99
type SelectOption,
1010
type SelectOptionOrSection,
1111
} from '@sentry/scraps/compactSelect';
@@ -533,24 +533,6 @@ export default function AssigneeSelectorDropdown({
533533
);
534534
};
535535

536-
const footerInviteButton = (
537-
<Flex align="center" gap="md">
538-
<Button
539-
size="xs"
540-
aria-label={t('Invite Member')}
541-
disabled={loading}
542-
onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
543-
event.preventDefault();
544-
openInviteMembersModal({source: 'assignee_selector'});
545-
}}
546-
icon={<IconAdd />}
547-
>
548-
{t('Invite Member')}
549-
</Button>
550-
{additionalMenuFooterItems}
551-
</Flex>
552-
);
553-
554536
return (
555537
<AssigneeWrapper>
556538
<CompactSelect
@@ -570,7 +552,21 @@ export default function AssigneeSelectorDropdown({
570552
onChange={handleSelect}
571553
options={makeAllOptions()}
572554
trigger={trigger ?? makeTrigger}
573-
menuFooter={footerInviteButton}
555+
menuFooter={
556+
<Flex gap="md">
557+
<MenuComponents.CTAButton
558+
disabled={loading}
559+
onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
560+
event.preventDefault();
561+
openInviteMembersModal({source: 'assignee_selector'});
562+
}}
563+
icon={<IconAdd />}
564+
>
565+
{t('Invite Member')}
566+
</MenuComponents.CTAButton>
567+
{additionalMenuFooterItems}
568+
</Flex>
569+
}
574570
sizeLimit={sizeLimit}
575571
sizeLimitMessage="Use search to find more users and teams..."
576572
strategy="fixed"

static/app/components/core/compactSelect/menuComponents.tsx

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,27 @@ import {
1010
type LinkButtonProps,
1111
} from '@sentry/scraps/button';
1212
import {Checkbox, type CheckboxProps} from '@sentry/scraps/checkbox';
13+
import {Text} from '@sentry/scraps/text';
1314

1415
import {t} from 'sentry/locale';
1516

1617
import {ControlContext} from './control';
1718

1819
export const MenuComponents = {
20+
/**
21+
* A button sized and styled to sit in `menuHeaderTrailingItems`. Inherits the
22+
* header's font size and renders in secondary text color so it blends with the
23+
* title rather than competing with it.
24+
*
25+
* Use this for lightweight, immediate actions in the header — e.g. "Reset",
26+
* "Clear", "Invite Member", or "Sync". These actions typically take effect
27+
* immediately and close the menu. For navigation actions use `LinkButton`
28+
* instead. For prominent footer actions use `CTAButton`.
29+
*/
30+
HeaderButton(props: DistributedOmit<ButtonProps, 'priority' | 'size'>) {
31+
return <StyledHeaderButton size="zero" priority="transparent" {...props} />;
32+
},
33+
1934
/**
2035
* A button sized and styled to sit in `menuHeaderTrailingItems`. Inherits the
2136
* header's font size and renders in secondary text color so it blends with the
@@ -32,7 +47,7 @@ export const MenuComponents = {
3247
const controlContext = useContext(ControlContext);
3348

3449
return (
35-
<HeaderButton
50+
<StyledHeaderButton
3651
size="zero"
3752
priority="transparent"
3853
{...props}
@@ -42,15 +57,15 @@ export const MenuComponents = {
4257
}}
4358
>
4459
{t('Reset')}
45-
</HeaderButton>
60+
</StyledHeaderButton>
4661
);
4762
},
4863

4964
ClearButton(props: DistributedOmit<ButtonProps, 'priority' | 'size' | 'children'>) {
5065
const controlContext = useContext(ControlContext);
5166

5267
return (
53-
<HeaderButton
68+
<StyledHeaderButton
5469
size="zero"
5570
priority="transparent"
5671
{...props}
@@ -60,7 +75,7 @@ export const MenuComponents = {
6075
}}
6176
>
6277
{t('Clear')}
63-
</HeaderButton>
78+
</StyledHeaderButton>
6479
);
6580
},
6681

@@ -77,10 +92,10 @@ export const MenuComponents = {
7792
* `priority` and `size` are locked to keep footer actions visually consistent.
7893
*/
7994
CTAButton(props: DistributedOmit<ButtonProps, 'priority' | 'size'>) {
80-
return <Button size="xs" {...props} />;
95+
return <Button size="xs" priority="default" {...props} />;
8196
},
8297
CTALinkButton(props: DistributedOmit<LinkButtonProps, 'priority' | 'size'>) {
83-
return <LinkButton size="xs" {...props} />;
98+
return <LinkButton size="xs" priority="default" {...props} />;
8499
},
85100

86101
/**
@@ -157,8 +172,12 @@ export const MenuComponents = {
157172
* breaking the menu's padding, and `showIcon` is locked to `false` to keep
158173
* the alert compact.
159174
*/
160-
Alert(props: DistributedOmit<AlertProps, 'system' | 'showIcon'>) {
161-
return <StyledAlert {...props} system={false} showIcon={false} />;
175+
Alert({children, ...props}: DistributedOmit<AlertProps, 'system' | 'showIcon'>) {
176+
return (
177+
<StyledAlert {...props} system={false} showIcon={false}>
178+
<Text size="sm">{children}</Text>
179+
</StyledAlert>
180+
);
162181
},
163182

164183
Checkbox(props: DistributedOmit<CheckboxProps, 'size'>) {
@@ -171,7 +190,7 @@ const StyledAlert = styled(Alert)`
171190
text-wrap: balance;
172191
`;
173192

174-
const HeaderButton = styled(Button)`
193+
const StyledHeaderButton = styled(Button)`
175194
font-size: inherit; /* Inherit font size from MenuHeader */
176195
font-weight: ${p => p.theme.font.weight.sans.regular};
177196
color: ${p => p.theme.tokens.content.secondary};

static/app/components/pageFilters/project/projectPageFilter.tsx

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import {Fragment, useCallback, useMemo, useRef, useState} from 'react';
2-
import styled from '@emotion/styled';
32
import {isAppleDevice} from '@react-aria/utils';
43
import isEqual from 'lodash/isEqual';
54
import partition from 'lodash/partition';
65
import sortBy from 'lodash/sortBy';
76

8-
import {Alert} from '@sentry/scraps/alert';
97
import {LinkButton} from '@sentry/scraps/button';
108
import {MenuComponents} from '@sentry/scraps/compactSelect';
119
import type {SelectOption, SelectOptionOrSection} from '@sentry/scraps/compactSelect';
@@ -468,17 +466,15 @@ export function ProjectPageFilter({
468466
selectionLimitExceeded || hasProjectWrite || stagedSelect.hasStagedChanges ? (
469467
<Stack gap="md" direction="column">
470468
{selectionLimitExceeded && (
471-
<CondensedAlert variant="warning" showIcon={false}>
472-
<Text size="sm">
473-
{tct(
474-
`You've selected [count] projects, but only up to [limit] can be selected at a time. Clear your selection to view all projects.`,
475-
{
476-
limit: SELECTION_COUNT_LIMIT,
477-
count: stagedValue.length,
478-
}
479-
)}
480-
</Text>
481-
</CondensedAlert>
469+
<MenuComponents.Alert variant="warning">
470+
{tct(
471+
`You've selected [count] projects, but only up to [limit] can be selected at a time. Clear your selection to view all projects.`,
472+
{
473+
limit: SELECTION_COUNT_LIMIT,
474+
count: stagedValue.length,
475+
}
476+
)}
477+
</MenuComponents.Alert>
482478
)}
483479
<Flex gap="md" align="center" justify={hasProjectWrite ? 'between' : 'end'}>
484480
{hasProjectWrite ? (
@@ -522,11 +518,6 @@ export function ProjectPageFilter({
522518
);
523519
}
524520

525-
const CondensedAlert = styled(Alert)`
526-
padding: ${p => p.theme.space.xs} ${p => p.theme.space.lg};
527-
text-wrap: balance;
528-
`;
529-
530521
function shouldCloseOnInteractOutside(target: Element) {
531522
// Don't close select menu when clicking on power hovercard ("Requires Business Plan") or disabled feature hovercard
532523
const powerHovercard = target.closest('[data-test-id="power-hovercard"]');

static/app/components/prevent/branchSelector/branchSelector.tsx

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@ import {useCallback, useEffect, useMemo, useState} from 'react';
22
import styled from '@emotion/styled';
33
import debounce from 'lodash/debounce';
44

5-
import {Button} from '@sentry/scraps/button';
65
import type {SelectOption} from '@sentry/scraps/compactSelect';
7-
import {CompactSelect} from '@sentry/scraps/compactSelect';
6+
import {CompactSelect, MenuComponents} from '@sentry/scraps/compactSelect';
87
import {Container, Flex} from '@sentry/scraps/layout';
98
import {OverlayTrigger} from '@sentry/scraps/overlayTrigger';
109

1110
import {useInfiniteRepositoryBranches} from 'sentry/components/prevent/branchSelector/useInfiniteRepositoryBranches';
1211
import {usePreventContext} from 'sentry/components/prevent/context/preventContext';
1312
import {IconBranch} from 'sentry/icons/iconBranch';
1413
import {t} from 'sentry/locale';
15-
import {space} from 'sentry/styles/space';
1614

1715
const ALL_BRANCHES = 'All Branches';
1816

@@ -89,28 +87,6 @@ export function BranchSelector() {
8987
};
9088
}, [handleOnSearch]);
9189

92-
const branchResetButton = useCallback(
93-
({closeOverlay}: any) => {
94-
if (!branch || branch === ALL_BRANCHES) {
95-
return null;
96-
}
97-
98-
return (
99-
<ResetButton
100-
onClick={() => {
101-
handleChange({value: ALL_BRANCHES});
102-
closeOverlay();
103-
}}
104-
size="zero"
105-
priority="transparent"
106-
>
107-
{t('Reset to all branches')}
108-
</ResetButton>
109-
);
110-
},
111-
[branch, handleChange]
112-
);
113-
11490
function getEmptyMessage() {
11591
if (isFetching) {
11692
return t('Getting branches...');
@@ -140,7 +116,19 @@ export function BranchSelector() {
140116
value={branch ?? ALL_BRANCHES}
141117
onChange={handleChange}
142118
onOpenChange={_ => setSearchValue(undefined)}
143-
menuHeaderTrailingItems={branchResetButton}
119+
menuHeaderTrailingItems={() => {
120+
if (!branch || branch === ALL_BRANCHES) {
121+
return null;
122+
}
123+
124+
return (
125+
<MenuComponents.ResetButton
126+
onClick={() => {
127+
handleChange({value: ALL_BRANCHES});
128+
}}
129+
/>
130+
);
131+
}}
144132
disabled={disabled}
145133
emptyMessage={getEmptyMessage()}
146134
closeOnSelect
@@ -182,11 +170,3 @@ const OptionLabel = styled('span')`
182170
margin: 0;
183171
}
184172
`;
185-
186-
const ResetButton = styled(Button)`
187-
font-size: inherit; /* Inherit font size from MenuHeader */
188-
font-weight: ${p => p.theme.font.weight.sans.regular};
189-
color: ${p => p.theme.tokens.content.secondary};
190-
padding: 0 ${space(0.5)};
191-
margin: -${space(0.5)} -${space(0.5)};
192-
`;

static/app/components/prevent/integratedOrgSelector/integratedOrgSelector.tsx

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
import {useCallback, useMemo} from 'react';
22
import styled from '@emotion/styled';
33

4-
import {LinkButton} from '@sentry/scraps/button';
54
import type {SelectOption} from '@sentry/scraps/compactSelect';
6-
import {CompactSelect} from '@sentry/scraps/compactSelect';
5+
import {CompactSelect, MenuComponents} from '@sentry/scraps/compactSelect';
76
import {Container, Flex, Grid} from '@sentry/scraps/layout';
87
import {ExternalLink} from '@sentry/scraps/link';
98
import {OverlayTrigger} from '@sentry/scraps/overlayTrigger';
10-
import {Text} from '@sentry/scraps/text';
119

1210
import Access from 'sentry/components/acl/access';
1311
import LoadingIndicator from 'sentry/components/loadingIndicator';
1412
import {usePreventContext} from 'sentry/components/prevent/context/preventContext';
1513
import {integratedOrgIdToName} from 'sentry/components/prevent/utils';
16-
import {IconAdd, IconBuilding, IconInfo} from 'sentry/icons';
14+
import {IconAdd, IconBuilding} from 'sentry/icons';
1715
import {t, tct} from 'sentry/locale';
1816
import type {Integration} from 'sentry/types/integrations';
1917
import getApiUrl from 'sentry/utils/api/getApiUrl';
@@ -62,19 +60,16 @@ function OrgFooterMessage() {
6260
<Flex gap="sm" direction="column" align="start">
6361
<Grid columns="max-content 1fr" gap="sm">
6462
{props => (
65-
<Text variant="muted" size="sm" {...props}>
66-
<IconInfo size="sm" />
67-
<div>
68-
{tct(
69-
'Installing the [githubAppLink:GitHub Application] will require admin approval.',
70-
{
71-
githubAppLink: (
72-
<ExternalLink openInNewTab href="https://github.com/apps/sentry" />
73-
),
74-
}
75-
)}
76-
</div>
77-
</Text>
63+
<MenuComponents.Alert variant="info" {...props}>
64+
{tct(
65+
'Installing the [githubAppLink:GitHub Application] will require admin approval.',
66+
{
67+
githubAppLink: (
68+
<ExternalLink openInNewTab href="https://github.com/apps/sentry" />
69+
),
70+
}
71+
)}
72+
</MenuComponents.Alert>
7873
)}
7974
</Grid>
8075
{isIntegrationInfoPending ? (
@@ -106,14 +101,13 @@ function OrgFooterMessage() {
106101
</Access>
107102
</IntegrationContext>
108103
) : (
109-
<LinkButton
104+
<MenuComponents.CTALinkButton
110105
href="https://github.com/apps/sentry/installations/select_target"
111-
size="xs"
112106
icon={<IconAdd />}
113107
external
114108
>
115109
{t('GitHub Organization')}
116-
</LinkButton>
110+
</MenuComponents.CTALinkButton>
117111
)}
118112
</Flex>
119113
);

0 commit comments

Comments
 (0)