mirror of
https://github.com/grafana/grafana.git
synced 2024-11-24 09:50:29 -06:00
UI: Add focus styles to QueryField component (#45933)
* Add focus styles to QueryField component
This commit is contained in:
parent
5c05a3deb9
commit
9067715d1d
@ -44,7 +44,7 @@ exports[`no enzyme tests`] = {
|
||||
"packages/grafana-ui/src/components/Logs/LogRows.test.tsx:2288254498": [
|
||||
[3, 17, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
"packages/grafana-ui/src/components/QueryField/QueryField.test.tsx:1906163280": [
|
||||
"packages/grafana-ui/src/components/QueryField/QueryField.test.tsx:1297745712": [
|
||||
[1, 19, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
"packages/grafana-ui/src/components/Slider/Slider.test.tsx:2110443485": [
|
||||
|
@ -1,30 +1,43 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { QueryField } from './QueryField';
|
||||
import { UnThemedQueryField } from './QueryField';
|
||||
import { Editor } from 'slate';
|
||||
import { createTheme } from '@grafana/data';
|
||||
|
||||
describe('<QueryField />', () => {
|
||||
it('should render with null initial value', () => {
|
||||
const wrapper = shallow(<QueryField query={null} onTypeahead={jest.fn()} portalOrigin="mock-origin" />);
|
||||
const wrapper = shallow(
|
||||
<UnThemedQueryField theme={createTheme()} query={null} onTypeahead={jest.fn()} portalOrigin="mock-origin" />
|
||||
);
|
||||
expect(wrapper.find('div').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render with empty initial value', () => {
|
||||
const wrapper = shallow(<QueryField query="" onTypeahead={jest.fn()} portalOrigin="mock-origin" />);
|
||||
const wrapper = shallow(
|
||||
<UnThemedQueryField theme={createTheme()} query="" onTypeahead={jest.fn()} portalOrigin="mock-origin" />
|
||||
);
|
||||
expect(wrapper.find('div').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render with initial value', () => {
|
||||
const wrapper = shallow(<QueryField query="my query" onTypeahead={jest.fn()} portalOrigin="mock-origin" />);
|
||||
const wrapper = shallow(
|
||||
<UnThemedQueryField theme={createTheme()} query="my query" onTypeahead={jest.fn()} portalOrigin="mock-origin" />
|
||||
);
|
||||
expect(wrapper.find('div').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should execute query on blur', () => {
|
||||
const onRun = jest.fn();
|
||||
const wrapper = shallow(
|
||||
<QueryField query="my query" onTypeahead={jest.fn()} onRunQuery={onRun} portalOrigin="mock-origin" />
|
||||
<UnThemedQueryField
|
||||
theme={createTheme()}
|
||||
query="my query"
|
||||
onTypeahead={jest.fn()}
|
||||
onRunQuery={onRun}
|
||||
portalOrigin="mock-origin"
|
||||
/>
|
||||
);
|
||||
const field = wrapper.instance() as QueryField;
|
||||
const field = wrapper.instance() as UnThemedQueryField;
|
||||
expect(onRun.mock.calls.length).toBe(0);
|
||||
field.handleBlur(new Event('bogus'), new Editor({}), () => {});
|
||||
expect(onRun.mock.calls.length).toBe(1);
|
||||
@ -33,9 +46,15 @@ describe('<QueryField />', () => {
|
||||
it('should run onChange with clean text', () => {
|
||||
const onChange = jest.fn();
|
||||
const wrapper = shallow(
|
||||
<QueryField query={`my\r clean query `} onTypeahead={jest.fn()} onChange={onChange} portalOrigin="mock-origin" />
|
||||
<UnThemedQueryField
|
||||
theme={createTheme()}
|
||||
query={`my\r clean query `}
|
||||
onTypeahead={jest.fn()}
|
||||
onChange={onChange}
|
||||
portalOrigin="mock-origin"
|
||||
/>
|
||||
);
|
||||
const field = wrapper.instance() as QueryField;
|
||||
const field = wrapper.instance() as UnThemedQueryField;
|
||||
field.runOnChange();
|
||||
expect(onChange.mock.calls.length).toBe(1);
|
||||
expect(onChange.mock.calls[0][0]).toBe('my clean query ');
|
||||
@ -45,7 +64,8 @@ describe('<QueryField />', () => {
|
||||
const onBlur = jest.fn();
|
||||
const onRun = jest.fn();
|
||||
const wrapper = shallow(
|
||||
<QueryField
|
||||
<UnThemedQueryField
|
||||
theme={createTheme()}
|
||||
query="my query"
|
||||
onTypeahead={jest.fn()}
|
||||
onBlur={onBlur}
|
||||
@ -53,7 +73,7 @@ describe('<QueryField />', () => {
|
||||
portalOrigin="mock-origin"
|
||||
/>
|
||||
);
|
||||
const field = wrapper.instance() as QueryField;
|
||||
const field = wrapper.instance() as UnThemedQueryField;
|
||||
expect(onBlur.mock.calls.length).toBe(0);
|
||||
expect(onRun.mock.calls.length).toBe(0);
|
||||
field.handleBlur(new Event('bogus'), new Editor({}), () => {});
|
||||
@ -62,14 +82,18 @@ describe('<QueryField />', () => {
|
||||
});
|
||||
describe('syntaxLoaded', () => {
|
||||
it('should re-render the editor after syntax has fully loaded', () => {
|
||||
const wrapper: any = shallow(<QueryField query="my query" portalOrigin="mock-origin" />);
|
||||
const wrapper: any = shallow(
|
||||
<UnThemedQueryField theme={createTheme()} query="my query" portalOrigin="mock-origin" />
|
||||
);
|
||||
const spyOnChange = jest.spyOn(wrapper.instance(), 'onChange').mockImplementation(jest.fn());
|
||||
wrapper.instance().editor = { insertText: () => ({ deleteBackward: () => ({ value: 'fooo' }) }) };
|
||||
wrapper.setProps({ syntaxLoaded: true });
|
||||
expect(spyOnChange).toHaveBeenCalledWith('fooo', true);
|
||||
});
|
||||
it('should not re-render the editor if syntax is already loaded', () => {
|
||||
const wrapper: any = shallow(<QueryField query="my query" portalOrigin="mock-origin" />);
|
||||
const wrapper: any = shallow(
|
||||
<UnThemedQueryField theme={createTheme()} query="my query" portalOrigin="mock-origin" />
|
||||
);
|
||||
const spyOnChange = jest.spyOn(wrapper.instance(), 'onChange').mockImplementation(jest.fn());
|
||||
wrapper.setProps({ syntaxLoaded: true });
|
||||
wrapper.instance().editor = {};
|
||||
@ -77,14 +101,18 @@ describe('<QueryField />', () => {
|
||||
expect(spyOnChange).not.toBeCalled();
|
||||
});
|
||||
it('should not re-render the editor if editor itself is not defined', () => {
|
||||
const wrapper: any = shallow(<QueryField query="my query" portalOrigin="mock-origin" />);
|
||||
const wrapper: any = shallow(
|
||||
<UnThemedQueryField theme={createTheme()} query="my query" portalOrigin="mock-origin" />
|
||||
);
|
||||
const spyOnChange = jest.spyOn(wrapper.instance(), 'onChange').mockImplementation(jest.fn());
|
||||
wrapper.setProps({ syntaxLoaded: true });
|
||||
expect(wrapper.instance().editor).toBeFalsy();
|
||||
expect(spyOnChange).not.toBeCalled();
|
||||
});
|
||||
it('should not re-render the editor twice once syntax is fully loaded', () => {
|
||||
const wrapper: any = shallow(<QueryField query="my query" portalOrigin="mock-origin" />);
|
||||
const wrapper: any = shallow(
|
||||
<UnThemedQueryField theme={createTheme()} query="my query" portalOrigin="mock-origin" />
|
||||
);
|
||||
const spyOnChange = jest.spyOn(wrapper.instance(), 'onChange').mockImplementation(jest.fn());
|
||||
wrapper.instance().editor = { insertText: () => ({ deleteBackward: () => ({ value: 'fooo' }) }) };
|
||||
wrapper.setProps({ syntaxLoaded: true });
|
||||
|
@ -16,10 +16,22 @@ import {
|
||||
SuggestionsPlugin,
|
||||
} from '../../slate-plugins';
|
||||
|
||||
import { makeValue, SCHEMA, CompletionItemGroup, TypeaheadOutput, TypeaheadInput, SuggestionsState } from '../..';
|
||||
import {
|
||||
makeValue,
|
||||
SCHEMA,
|
||||
CompletionItemGroup,
|
||||
TypeaheadOutput,
|
||||
TypeaheadInput,
|
||||
SuggestionsState,
|
||||
Themeable2,
|
||||
} from '../..';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { withTheme2 } from '../../themes';
|
||||
import { getFocusStyles } from '../../themes/mixins';
|
||||
|
||||
export interface QueryFieldProps {
|
||||
export interface QueryFieldProps extends Themeable2 {
|
||||
additionalPlugins?: Plugin[];
|
||||
cleanText?: (text: string) => string;
|
||||
disabled?: boolean;
|
||||
@ -38,6 +50,7 @@ export interface QueryFieldProps {
|
||||
portalOrigin: string;
|
||||
syntax?: string;
|
||||
syntaxLoaded?: boolean;
|
||||
theme: GrafanaTheme2;
|
||||
}
|
||||
|
||||
export interface QueryFieldState {
|
||||
@ -54,7 +67,7 @@ export interface QueryFieldState {
|
||||
* This component can only process strings. Internally it uses Slate Value.
|
||||
* Implement props.onTypeahead to use suggestions, see PromQueryField.tsx as an example.
|
||||
*/
|
||||
export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldState> {
|
||||
export class UnThemedQueryField extends React.PureComponent<QueryFieldProps, QueryFieldState> {
|
||||
plugins: Plugin[];
|
||||
runOnChangeDebounced: Function;
|
||||
lastExecutedValue: Value | null = null;
|
||||
@ -197,13 +210,14 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
}
|
||||
|
||||
render() {
|
||||
const { disabled } = this.props;
|
||||
const { disabled, theme } = this.props;
|
||||
const wrapperClassName = classnames('slate-query-field__wrapper', {
|
||||
'slate-query-field__wrapper--disabled': disabled,
|
||||
});
|
||||
const styles = getStyles(theme);
|
||||
|
||||
return (
|
||||
<div className={wrapperClassName}>
|
||||
<div className={cx(wrapperClassName, styles.wrapper)}>
|
||||
<div className="slate-query-field" aria-label={selectors.components.QueryField.container}>
|
||||
<Editor
|
||||
ref={(editor) => (this.editor = editor!)}
|
||||
@ -227,4 +241,15 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
}
|
||||
}
|
||||
|
||||
export default QueryField;
|
||||
export const QueryField = withTheme2(UnThemedQueryField);
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
const focusStyles = getFocusStyles(theme);
|
||||
return {
|
||||
wrapper: css`
|
||||
&:focus-within {
|
||||
${focusStyles}
|
||||
}
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user