Skip to content

Commit 83c547a

Browse files
feat(ui): Add priority prop to DropdownMenuItemV2 (#31693)
* ref(ui): Use DropdownMenuV2 for dashboard widget dropdown * fix(test): Update tests related to dashboard widget dropdowns Necessary given the new implementation of DropdownMenuV2. * fix(acceptance): Update acceptance tests for Dashboards * feat(ui): Add priority prop to DropdownMenuItemV2 To optionally use a different item color (danger = red, primary = purple). * ref(ui): Use danger priority for delete actions in DropdownMenuV2 * fix(tsc): Add missing MenuItemProps type * ref(ui): Limit query card dropdown height * style: Use type Priority instead of 'primary' | 'danger' * style(lint): Auto commit lint changes Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
1 parent 3bff398 commit 83c547a

File tree

5 files changed

+72
-19
lines changed

5 files changed

+72
-19
lines changed

static/app/components/dropdownMenuItemV2.tsx

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,14 @@ import {Node} from '@react-types/shared';
1010
import {IconChevron} from 'sentry/icons';
1111
import overflowEllipsis from 'sentry/styles/overflowEllipsis';
1212
import space from 'sentry/styles/space';
13+
import {Theme} from 'sentry/utils/theme';
1314

15+
type Priority = 'primary' | 'danger';
1416
export type MenuItemProps = {
1517
/**
1618
* Item key. Must be unique across the entire menu, including sub-menus.
1719
*/
1820
key: string;
19-
/**
20-
* Item label. Should prefereably be a string. If not, make sure that
21-
* there are appropriate aria-labels.
22-
*/
23-
label: React.ReactNode;
2421
/**
2522
* Sub-items that are nested inside this item. By default, sub-items are
2623
* rendered collectively as menu sections inside the current menu. If
@@ -37,6 +34,11 @@ export type MenuItemProps = {
3734
* when `children` is also defined.
3835
*/
3936
isSubmenu?: boolean;
37+
/**
38+
* Item label. Should prefereably be a string. If not, make sure that
39+
* there are appropriate aria-labels.
40+
*/
41+
label?: React.ReactNode;
4042
/*
4143
* Items to be added to the left of the label
4244
*/
@@ -52,6 +54,11 @@ export type MenuItemProps = {
5254
* item's key is passed as an argument.
5355
*/
5456
onAction?: (key: MenuItemProps['key']) => void;
57+
/**
58+
* Accented text and background (on hover) colors. Primary = purple, and
59+
* danger = red.
60+
*/
61+
priority?: Priority;
5562
/**
5663
* Whether to show a line divider below this menu item
5764
*/
@@ -194,6 +201,7 @@ const MenuItem = withRouter(
194201
keyboardProps
195202
);
196203
const {
204+
priority,
197205
details,
198206
leadingItems,
199207
leadingItemsSpanFullHeight,
@@ -208,11 +216,13 @@ const MenuItem = withRouter(
208216
ref={ref}
209217
as={renderAs}
210218
isDisabled={isDisabled}
219+
priority={priority}
211220
data-test-id={item.key}
221+
{...(item.to && {'data-test-href': item.to})}
212222
{...props}
213223
{...(isSubmenuTrigger && {role: 'menuitemradio'})}
214224
>
215-
<InnerWrap isFocused={isFocused}>
225+
<InnerWrap isFocused={isFocused} priority={priority}>
216226
{leadingItems && (
217227
<LeadingItems
218228
isDisabled={isDisabled}
@@ -223,10 +233,18 @@ const MenuItem = withRouter(
223233
)}
224234
<ContentWrap isFocused={isFocused} showDividers={showDividers}>
225235
<LabelWrap>
226-
<Label isDisabled={isDisabled} {...labelProps} aria-hidden="true">
236+
<Label {...labelProps} aria-hidden="true">
227237
{label}
228238
</Label>
229-
{details && <Details {...descriptionProps}>{details}</Details>}
239+
{details && (
240+
<Details
241+
isDisabled={isDisabled}
242+
priority={priority}
243+
{...descriptionProps}
244+
>
245+
{details}
246+
</Details>
247+
)}
230248
</LabelWrap>
231249
{(trailingItems || isSubmenuTrigger) && (
232250
<TrailingItems
@@ -247,16 +265,26 @@ const MenuItem = withRouter(
247265
);
248266
export default MenuItem;
249267

250-
const MenuItemWrap = styled('li')<{isDisabled?: boolean}>`
268+
const MenuItemWrap = styled('li')<{
269+
isDisabled?: boolean;
270+
isFocused?: boolean;
271+
priority?: Priority;
272+
}>`
251273
position: static;
252274
list-style-type: none;
253275
margin: 0;
254276
padding: 0 ${space(0.5)};
255277
cursor: pointer;
256278
257279
color: ${p => p.theme.textColor};
258-
259-
${p => p.isDisabled && `cursor: initial;`}
280+
${p => p.priority === 'primary' && `color: ${p.theme.activeText};`}
281+
${p => p.priority === 'danger' && `color: ${p.theme.errorText};`}
282+
${p =>
283+
p.isDisabled &&
284+
`
285+
color: ${p.theme.subText};
286+
cursor: initial;
287+
`}
260288
261289
&:focus {
262290
outline: none;
@@ -266,14 +294,30 @@ const MenuItemWrap = styled('li')<{isDisabled?: boolean}>`
266294
}
267295
`;
268296

269-
const InnerWrap = styled('div')<{isFocused: boolean}>`
297+
const getHoverBackground = (theme: Theme, priority?: Priority) => {
298+
let hoverBackground: string;
299+
switch (priority) {
300+
case 'primary':
301+
hoverBackground = theme.purple100;
302+
break;
303+
case 'danger':
304+
hoverBackground = theme.red100;
305+
break;
306+
default:
307+
hoverBackground = theme.hover;
308+
}
309+
310+
return `background: ${hoverBackground}; z-index: 1;`;
311+
};
312+
313+
const InnerWrap = styled('div')<{isFocused: boolean; priority?: Priority}>`
270314
display: flex;
271315
position: relative;
272316
padding: 0 ${space(1)};
273317
border-radius: ${p => p.theme.borderRadius};
274318
box-sizing: border-box;
275319
276-
${p => p.isFocused && `background: ${p.theme.hover}; z-index: 1;`}
320+
${p => p.isFocused && getHoverBackground(p.theme, p.priority)}
277321
`;
278322

279323
const LeadingItems = styled('div')<{isDisabled?: boolean; spanFullHeight?: boolean}>`
@@ -319,21 +363,23 @@ const LabelWrap = styled('div')`
319363
width: 100%;
320364
`;
321365

322-
const Label = styled('p')<{isDisabled?: boolean}>`
366+
const Label = styled('p')`
323367
margin-bottom: 0;
324368
line-height: 1.4;
325369
white-space: nowrap;
326370
${overflowEllipsis}
327-
328-
${p => p.isDisabled && `color: ${p.theme.subText};`}
329371
`;
330372

331-
const Details = styled('p')`
373+
const Details = styled('p')<{isDisabled: boolean; priority?: Priority}>`
332374
font-size: ${p => p.theme.fontSizeSmall};
333375
color: ${p => p.theme.subText};
334376
line-height: 1.2;
335377
margin-bottom: 0;
336378
${overflowEllipsis}
379+
380+
${p => p.priority === 'primary' && `color: ${p.theme.activeText};`}
381+
${p => p.priority === 'danger' && `color: ${p.theme.errorText};`}
382+
${p => p.isDisabled && `color: ${p.theme.subText};`}
337383
`;
338384

339385
const TrailingItems = styled('div')<{isDisabled?: boolean; spanFullHeight?: boolean}>`

static/app/utils/theme.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,13 @@ const generateAliases = (colors: BaseColors) => ({
200200
* A color that denotes a "success", or something good
201201
*/
202202
success: colors.green300,
203+
successText: colors.green400,
203204

204205
/**
205206
* A color that denotes an error, or something that is wrong
206207
*/
207208
error: colors.red300,
209+
errorText: colors.red400,
208210

209211
/**
210212
* A color that indicates something is disabled where user can not interact or use
@@ -223,6 +225,7 @@ const generateAliases = (colors: BaseColors) => ({
223225
*/
224226
active: colors.purple300,
225227
activeHover: colors.purple400,
228+
activeText: colors.purple400,
226229

227230
/**
228231
* Indicates that something has "focus", which is different than "active" state as it is more temporal

static/app/views/dashboardsV2/manage/dashboardList.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {Client} from 'sentry/api';
1313
import Button from 'sentry/components/button';
1414
import {openConfirmModal} from 'sentry/components/confirm';
1515
import DropdownMenuControlV2 from 'sentry/components/dropdownMenuControlV2';
16+
import {MenuItemProps} from 'sentry/components/dropdownMenuItemV2';
1617
import EmptyStateWarning from 'sentry/components/emptyStateWarning';
1718
import Pagination from 'sentry/components/pagination';
1819
import TimeSince from 'sentry/components/timeSince';
@@ -83,7 +84,7 @@ function DashboardList({
8384
}
8485

8586
function renderDropdownMenu(dashboard: DashboardListItem) {
86-
const menuItems = [
87+
const menuItems: MenuItemProps[] = [
8788
{
8889
key: 'dashboard-duplicate',
8990
label: t('Duplicate'),
@@ -94,6 +95,7 @@ function DashboardList({
9495
key: 'dashboard-delete',
9596
label: t('Delete'),
9697
leadingItems: <IconDelete />,
98+
priority: 'danger',
9799
onAction: () => {
98100
openConfirmModal({
99101
message: t('Are you sure you want to delete this dashboard?'),

static/app/views/dashboardsV2/widgetCard/widgetCardContextMenu.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ function WidgetCardContextMenu({
202202
key: 'delete-widget',
203203
label: t('Delete Widget'),
204204
leadingItems: <IconDelete />,
205+
priority: 'danger',
205206
onAction: () => {
206207
openConfirmModal({
207208
message: t('Are you sure you want to delete this widget?'),

static/app/views/eventsV2/queryList.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ class QueryList extends React.Component<Props> {
273273
const dateStatus = <TimeSince date={savedQuery.dateUpdated} />;
274274
const referrer = `api.discover.${eventView.getDisplayMode()}-chart`;
275275

276-
const menuItems = (canAddToDashboard: boolean) => [
276+
const menuItems = (canAddToDashboard: boolean): MenuItemProps[] => [
277277
...(canAddToDashboard
278278
? [
279279
{
@@ -295,6 +295,7 @@ class QueryList extends React.Component<Props> {
295295
key: 'delete',
296296
label: t('Delete Query'),
297297
leadingItems: <IconDelete />,
298+
priority: 'danger',
298299
onAction: () => this.handleDeleteQuery(eventView),
299300
},
300301
];

0 commit comments

Comments
 (0)