From 9067715d1d2e4405d8ecb89c7baba1571261f978 Mon Sep 17 00:00:00 2001 From: Connor Lindsey Date: Wed, 2 Mar 2022 08:33:14 -0700 Subject: [PATCH] UI: Add focus styles to QueryField component (#45933) * Add focus styles to QueryField component --- .betterer.results | 2 +- .../components/QueryField/QueryField.test.tsx | 56 ++++++++++++++----- .../src/components/QueryField/QueryField.tsx | 37 ++++++++++-- 3 files changed, 74 insertions(+), 21 deletions(-) diff --git a/.betterer.results b/.betterer.results index 7919254746f..a61180ce407 100644 --- a/.betterer.results +++ b/.betterer.results @@ -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": [ diff --git a/packages/grafana-ui/src/components/QueryField/QueryField.test.tsx b/packages/grafana-ui/src/components/QueryField/QueryField.test.tsx index 1e4deaf59e6..fdfee01c17f 100644 --- a/packages/grafana-ui/src/components/QueryField/QueryField.test.tsx +++ b/packages/grafana-ui/src/components/QueryField/QueryField.test.tsx @@ -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('', () => { it('should render with null initial value', () => { - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(wrapper.find('div').exists()).toBeTruthy(); }); it('should render with empty initial value', () => { - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(wrapper.find('div').exists()).toBeTruthy(); }); it('should render with initial value', () => { - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(wrapper.find('div').exists()).toBeTruthy(); }); it('should execute query on blur', () => { const onRun = jest.fn(); const wrapper = shallow( - + ); - 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('', () => { it('should run onChange with clean text', () => { const onChange = jest.fn(); const wrapper = shallow( - + ); - 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('', () => { const onBlur = jest.fn(); const onRun = jest.fn(); const wrapper = shallow( - ', () => { 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('', () => { }); describe('syntaxLoaded', () => { it('should re-render the editor after syntax has fully loaded', () => { - const wrapper: any = shallow(); + const wrapper: any = shallow( + + ); 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(); + const wrapper: any = shallow( + + ); const spyOnChange = jest.spyOn(wrapper.instance(), 'onChange').mockImplementation(jest.fn()); wrapper.setProps({ syntaxLoaded: true }); wrapper.instance().editor = {}; @@ -77,14 +101,18 @@ describe('', () => { expect(spyOnChange).not.toBeCalled(); }); it('should not re-render the editor if editor itself is not defined', () => { - const wrapper: any = shallow(); + const wrapper: any = shallow( + + ); 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(); + const wrapper: any = shallow( + + ); const spyOnChange = jest.spyOn(wrapper.instance(), 'onChange').mockImplementation(jest.fn()); wrapper.instance().editor = { insertText: () => ({ deleteBackward: () => ({ value: 'fooo' }) }) }; wrapper.setProps({ syntaxLoaded: true }); diff --git a/packages/grafana-ui/src/components/QueryField/QueryField.tsx b/packages/grafana-ui/src/components/QueryField/QueryField.tsx index 1191a70380e..bf22dac6fe0 100644 --- a/packages/grafana-ui/src/components/QueryField/QueryField.tsx +++ b/packages/grafana-ui/src/components/QueryField/QueryField.tsx @@ -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 { +export class UnThemedQueryField extends React.PureComponent { plugins: Plugin[]; runOnChangeDebounced: Function; lastExecutedValue: Value | null = null; @@ -197,13 +210,14 @@ export class QueryField extends React.PureComponent +
(this.editor = editor!)} @@ -227,4 +241,15 @@ export class QueryField extends React.PureComponent { + const focusStyles = getFocusStyles(theme); + return { + wrapper: css` + &:focus-within { + ${focusStyles} + } + `, + }; +};