diff --git a/packages/grafana-ui/src/components/Typeahead/Typeahead.test.tsx b/packages/grafana-ui/src/components/Typeahead/Typeahead.test.tsx new file mode 100644 index 00000000000..34cac0ba896 --- /dev/null +++ b/packages/grafana-ui/src/components/Typeahead/Typeahead.test.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { mount } from 'enzyme'; + +import { Typeahead, State } from './Typeahead'; +import { TypeaheadItem } from './TypeaheadItem'; +import { CompletionItemKind } from '../../types'; + +describe('Typeahead', () => { + const completionItemGroups = [{ label: 'my group', items: [{ label: 'first item' }] }]; + describe('when closed', () => { + it('renders nothing when no items given', () => { + const component = mount(); + expect(component.find('.typeahead')).toHaveLength(0); + }); + it('renders nothing when items given', () => { + const component = mount(); + expect(component.find('.typeahead')).toHaveLength(0); + }); + }); + describe('when open', () => { + it('renders given items and nothing is selected', () => { + const component = mount(); + expect(component.find('.typeahead')).toHaveLength(1); + const items = component.find(TypeaheadItem); + expect(items).toHaveLength(2); + expect(items.get(0).props.item.kind).toEqual(CompletionItemKind.GroupTitle); + expect(items.get(0).props.isSelected).toBeFalsy(); + expect(items.get(1).props.item.label).toEqual('first item'); + expect(items.get(1).props.isSelected).toBeFalsy(); + }); + }); + it('selected the first non-group item on moving to first item', () => { + const component = mount(); + expect(component.find('.typeahead')).toHaveLength(1); + let items = component.find(TypeaheadItem); + + expect(items).toHaveLength(2); + expect((component.state() as State).typeaheadIndex).toBe(null); + (component.instance() as Typeahead).moveMenuIndex(1); + expect((component.state() as State).typeaheadIndex).toBe(1); + component.setProps({}); + items = component.find(TypeaheadItem); + expect(items.get(0).props.isSelected).toBeFalsy(); + expect(items.get(1).props.isSelected).toBeTruthy(); + }); +}); diff --git a/packages/grafana-ui/src/components/Typeahead/Typeahead.tsx b/packages/grafana-ui/src/components/Typeahead/Typeahead.tsx index 10f5c3d8736..20526e17d9c 100644 --- a/packages/grafana-ui/src/components/Typeahead/Typeahead.tsx +++ b/packages/grafana-ui/src/components/Typeahead/Typeahead.tsx @@ -20,13 +20,13 @@ interface Props { isOpen?: boolean; } -interface State { +export interface State { allItems: CompletionItem[]; listWidth: number; listHeight: number; itemHeight: number; hoveredItem: number | null; - typeaheadIndex: number; + typeaheadIndex: number | null; } export class Typeahead extends React.PureComponent { @@ -34,7 +34,14 @@ export class Typeahead extends React.PureComponent { context!: React.ContextType; listRef = createRef(); - state: State = { hoveredItem: null, typeaheadIndex: 1, allItems: [], listWidth: -1, listHeight: -1, itemHeight: -1 }; + state: State = { + hoveredItem: null, + typeaheadIndex: null, + allItems: [], + listWidth: -1, + listHeight: -1, + itemHeight: -1, + }; componentDidMount = () => { if (this.props.menuRef) { @@ -63,7 +70,12 @@ export class Typeahead extends React.PureComponent { }; componentDidUpdate = (prevProps: Readonly, prevState: Readonly) => { - if (prevState.typeaheadIndex !== this.state.typeaheadIndex && this.listRef && this.listRef.current) { + if ( + this.state.typeaheadIndex !== null && + prevState.typeaheadIndex !== this.state.typeaheadIndex && + this.listRef && + this.listRef.current + ) { if (this.state.typeaheadIndex === 1) { this.listRef.current.scrollToItem(0); // special case for handling the first group label return; @@ -75,7 +87,7 @@ export class Typeahead extends React.PureComponent { const allItems = flattenGroupItems(this.props.groupedItems); const longestLabel = calculateLongestLabel(allItems); const { listWidth, listHeight, itemHeight } = calculateListSizes(this.context, allItems, longestLabel); - this.setState({ listWidth, listHeight, itemHeight, allItems }); + this.setState({ listWidth, listHeight, itemHeight, allItems, typeaheadIndex: null }); } }; @@ -95,7 +107,8 @@ export class Typeahead extends React.PureComponent { const itemCount = this.state.allItems.length; if (itemCount) { // Select next suggestion - let newTypeaheadIndex = modulo(this.state.typeaheadIndex + moveAmount, itemCount); + const typeaheadIndex = this.state.typeaheadIndex || 0; + let newTypeaheadIndex = modulo(typeaheadIndex + moveAmount, itemCount); if (this.state.allItems[newTypeaheadIndex].kind === CompletionItemKind.GroupTitle) { newTypeaheadIndex = modulo(newTypeaheadIndex + moveAmount, itemCount); @@ -110,7 +123,7 @@ export class Typeahead extends React.PureComponent { }; insertSuggestion = () => { - if (this.props.onSelectSuggestion) { + if (this.props.onSelectSuggestion && this.state.typeaheadIndex !== null) { this.props.onSelectSuggestion(this.state.allItems[this.state.typeaheadIndex]); } }; @@ -144,6 +157,7 @@ export class Typeahead extends React.PureComponent { const { allItems, listWidth, listHeight, itemHeight, hoveredItem, typeaheadIndex } = this.state; const showDocumentation = hoveredItem || typeaheadIndex; + const documentationItem = allItems[hoveredItem ? hoveredItem : typeaheadIndex || 0]; return ( @@ -169,7 +183,7 @@ export class Typeahead extends React.PureComponent { return ( (this.props.onSelectSuggestion ? this.props.onSelectSuggestion(item) : {})} - isSelected={allItems[typeaheadIndex] === item} + isSelected={typeaheadIndex === null ? false : allItems[typeaheadIndex] === item} item={item} prefix={prefix} style={style} @@ -181,9 +195,7 @@ export class Typeahead extends React.PureComponent { - {showDocumentation && ( - - )} + {showDocumentation && } ); } diff --git a/public/sass/components/_slate_editor.scss b/public/sass/components/_slate_editor.scss index 1870794c774..010180aa4e6 100644 --- a/public/sass/components/_slate_editor.scss +++ b/public/sass/components/_slate_editor.scss @@ -35,7 +35,6 @@ border: $panel-border; max-height: calc(66vh); overflow-y: scroll; - max-width: calc(66%); overflow-x: hidden; outline: none; list-style: none;