|
@@ -1,11 +1,15 @@
|
|
import {Fragment, PureComponent} from 'react';
|
|
import {Fragment, PureComponent} from 'react';
|
|
import styled from '@emotion/styled';
|
|
import styled from '@emotion/styled';
|
|
|
|
+import color from 'color';
|
|
|
|
|
|
import LoadingIndicator from 'sentry/components/loadingIndicator';
|
|
import LoadingIndicator from 'sentry/components/loadingIndicator';
|
|
import {t} from 'sentry/locale';
|
|
import {t} from 'sentry/locale';
|
|
import space from 'sentry/styles/space';
|
|
import space from 'sentry/styles/space';
|
|
|
|
|
|
-import {ItemType, SearchGroup, SearchItem} from './types';
|
|
|
|
|
|
+import Button from '../button';
|
|
|
|
+import HotkeysLabel from '../hotkeysLabel';
|
|
|
|
+
|
|
|
|
+import {ItemType, QuickAction, SearchGroup, SearchItem} from './types';
|
|
|
|
|
|
type Props = {
|
|
type Props = {
|
|
items: SearchGroup[];
|
|
items: SearchGroup[];
|
|
@@ -13,6 +17,9 @@ type Props = {
|
|
onClick: (value: string, item: SearchItem) => void;
|
|
onClick: (value: string, item: SearchItem) => void;
|
|
searchSubstring: string;
|
|
searchSubstring: string;
|
|
className?: string;
|
|
className?: string;
|
|
|
|
+ maxMenuHeight?: number;
|
|
|
|
+ runQuickAction?: (action: QuickAction) => void;
|
|
|
|
+ visibleActions?: QuickAction[];
|
|
};
|
|
};
|
|
|
|
|
|
class SearchDropdown extends PureComponent<Props> {
|
|
class SearchDropdown extends PureComponent<Props> {
|
|
@@ -75,7 +82,8 @@ class SearchDropdown extends PureComponent<Props> {
|
|
);
|
|
);
|
|
|
|
|
|
render() {
|
|
render() {
|
|
- const {className, loading, items} = this.props;
|
|
|
|
|
|
+ const {className, loading, items, runQuickAction, visibleActions, maxMenuHeight} =
|
|
|
|
+ this.props;
|
|
return (
|
|
return (
|
|
<StyledSearchDropdown className={className}>
|
|
<StyledSearchDropdown className={className}>
|
|
{loading ? (
|
|
{loading ? (
|
|
@@ -83,7 +91,7 @@ class SearchDropdown extends PureComponent<Props> {
|
|
<LoadingIndicator mini />
|
|
<LoadingIndicator mini />
|
|
</LoadingWrapper>
|
|
</LoadingWrapper>
|
|
) : (
|
|
) : (
|
|
- <SearchItemsList>
|
|
|
|
|
|
+ <SearchItemsList maxMenuHeight={maxMenuHeight}>
|
|
{items.map(item => {
|
|
{items.map(item => {
|
|
const isEmpty = item.children && !item.children.length;
|
|
const isEmpty = item.children && !item.children.length;
|
|
const invalidTag = item.type === ItemType.INVALID_TAG;
|
|
const invalidTag = item.type === ItemType.INVALID_TAG;
|
|
@@ -100,6 +108,30 @@ class SearchDropdown extends PureComponent<Props> {
|
|
})}
|
|
})}
|
|
</SearchItemsList>
|
|
</SearchItemsList>
|
|
)}
|
|
)}
|
|
|
|
+ <DropdownFooter>
|
|
|
|
+ <ActionsRow>
|
|
|
|
+ {runQuickAction &&
|
|
|
|
+ visibleActions?.map(action => {
|
|
|
|
+ return (
|
|
|
|
+ <ActionButtonContainer
|
|
|
|
+ key={action.text}
|
|
|
|
+ onClick={() => runQuickAction(action)}
|
|
|
|
+ >
|
|
|
|
+ <HotkeyGlyphWrapper>
|
|
|
|
+ <HotkeysLabel value={action.hotkeys?.display ?? []} />
|
|
|
|
+ </HotkeyGlyphWrapper>
|
|
|
|
+ <HotkeyTitle>{action.text}</HotkeyTitle>
|
|
|
|
+ </ActionButtonContainer>
|
|
|
|
+ );
|
|
|
|
+ })}
|
|
|
|
+ </ActionsRow>
|
|
|
|
+ <Button
|
|
|
|
+ size="xsmall"
|
|
|
|
+ href="https://docs.sentry.io/product/sentry-basics/search/"
|
|
|
|
+ >
|
|
|
|
+ Read the docs
|
|
|
|
+ </Button>
|
|
|
|
+ </DropdownFooter>
|
|
</StyledSearchDropdown>
|
|
</StyledSearchDropdown>
|
|
);
|
|
);
|
|
}
|
|
}
|
|
@@ -164,10 +196,22 @@ const SearchDropdownGroupTitle = styled('header')`
|
|
}
|
|
}
|
|
`;
|
|
`;
|
|
|
|
|
|
-const SearchItemsList = styled('ul')`
|
|
|
|
|
|
+const SearchItemsList = styled('ul')<{maxMenuHeight?: number}>`
|
|
padding-left: 0;
|
|
padding-left: 0;
|
|
list-style: none;
|
|
list-style: none;
|
|
margin-bottom: 0;
|
|
margin-bottom: 0;
|
|
|
|
+ ${p => {
|
|
|
|
+ if (p.maxMenuHeight !== undefined) {
|
|
|
|
+ return `
|
|
|
|
+ max-height: ${p.maxMenuHeight}px;
|
|
|
|
+ overflow-y: scroll;
|
|
|
|
+ `;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return `
|
|
|
|
+ height: auto;
|
|
|
|
+ `;
|
|
|
|
+ }}
|
|
`;
|
|
`;
|
|
|
|
|
|
const SearchListItem = styled(ListItem)`
|
|
const SearchListItem = styled(ListItem)`
|
|
@@ -202,3 +246,46 @@ const Documentation = styled('span')`
|
|
float: right;
|
|
float: right;
|
|
color: ${p => p.theme.gray300};
|
|
color: ${p => p.theme.gray300};
|
|
`;
|
|
`;
|
|
|
|
+
|
|
|
|
+const DropdownFooter = styled(`div`)`
|
|
|
|
+ width: 100%;
|
|
|
|
+ min-height: 45px;
|
|
|
|
+ background-color: ${p => p.theme.backgroundSecondary};
|
|
|
|
+ border-top: 1px solid ${p => p.theme.innerBorder};
|
|
|
|
+ flex-direction: row;
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ justify-content: space-between;
|
|
|
|
+ padding: ${space(1)};
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
+`;
|
|
|
|
+
|
|
|
|
+const ActionsRow = styled('div')`
|
|
|
|
+ flex-direction: row;
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+`;
|
|
|
|
+
|
|
|
|
+const ActionButtonContainer = styled('div')`
|
|
|
|
+ display: flex;
|
|
|
|
+ flex-direction: row;
|
|
|
|
+ align-items: center;
|
|
|
|
+ height: auto;
|
|
|
|
+ padding: 0 ${space(1.5)};
|
|
|
|
+
|
|
|
|
+ cursor: pointer;
|
|
|
|
+
|
|
|
|
+ :hover {
|
|
|
|
+ border-radius: ${p => p.theme.borderRadius};
|
|
|
|
+ background-color: ${p => color(p.theme.hover).darken(0.02).string()};
|
|
|
|
+ }
|
|
|
|
+`;
|
|
|
|
+
|
|
|
|
+const HotkeyGlyphWrapper = styled('span')`
|
|
|
|
+ color: ${p => p.theme.gray300};
|
|
|
|
+ margin-right: ${space(0.5)};
|
|
|
|
+`;
|
|
|
|
+
|
|
|
|
+const HotkeyTitle = styled(`span`)`
|
|
|
|
+ font-size: ${p => p.theme.fontSizeSmall};
|
|
|
|
+`;
|