Added color options for highlighting search terms
Summary: This diff builds on the previous ones by enabling other colors to be used as highlights for the search terms. Current color options are: yellow(default), red, blue, green. Possible extensions to this feature could include allow the user to enter a custom hex-color string and use that as the highlight color. Changelog: DataTable will now have option to have its search terms highlighted in the search results by toggling and customizing the highlight colors in the menu bar Reviewed By: mweststrate Differential Revision: D37383163 fbshipit-source-id: c81e383c0570ef5efbf3171b92b81a8fb2e55ea7
This commit is contained in:
committed by
Facebook GitHub Bot
parent
2f39ede6f7
commit
f46cf2b0ce
@@ -7,7 +7,6 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import React, {
|
import React, {
|
||||||
useEffect,
|
useEffect,
|
||||||
memo,
|
memo,
|
||||||
@@ -19,19 +18,20 @@ import React, {
|
|||||||
import {debounce} from 'lodash';
|
import {debounce} from 'lodash';
|
||||||
import {theme} from './theme';
|
import {theme} from './theme';
|
||||||
|
|
||||||
const Highlighted = styled.span({
|
|
||||||
backgroundColor: theme.searchHighlightBackground,
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface HighlightManager {
|
export interface HighlightManager {
|
||||||
setFilter(text: string | undefined): void;
|
setFilter(text: string | undefined): void;
|
||||||
render(text: string): React.ReactNode;
|
render(text: string): React.ReactNode;
|
||||||
|
setHighlightColor(color: string | undefined): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createHighlightManager(initialText: string = ''): HighlightManager {
|
function createHighlightManager(
|
||||||
|
initialText: string = '',
|
||||||
|
initialHighlightColor: string = theme.searchHighlightBackground.yellow,
|
||||||
|
): HighlightManager {
|
||||||
const callbacks = new Set<(prev: string, next: string) => void>();
|
const callbacks = new Set<(prev: string, next: string) => void>();
|
||||||
let matches = 0;
|
let matches = 0;
|
||||||
let currentFilter = initialText;
|
let currentFilter = initialText;
|
||||||
|
let currHighlightColor = initialHighlightColor;
|
||||||
|
|
||||||
const Highlight: React.FC<{text: string}> = memo(({text}) => {
|
const Highlight: React.FC<{text: string}> = memo(({text}) => {
|
||||||
const [, setUpdate] = useState(0);
|
const [, setUpdate] = useState(0);
|
||||||
@@ -65,9 +65,9 @@ function createHighlightManager(initialText: string = ''): HighlightManager {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{text.substr(0, index)}
|
{text.substr(0, index)}
|
||||||
<Highlighted>
|
<span style={{backgroundColor: currHighlightColor}}>
|
||||||
{text.substr(index, currentFilter.length)}
|
{text.substr(index, currentFilter.length)}
|
||||||
</Highlighted>
|
</span>
|
||||||
{text.substr(index + currentFilter.length)}
|
{text.substr(index + currentFilter.length)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -87,6 +87,12 @@ function createHighlightManager(initialText: string = ''): HighlightManager {
|
|||||||
render(text: string) {
|
render(text: string) {
|
||||||
return <Highlight text={text} />;
|
return <Highlight text={text} />;
|
||||||
},
|
},
|
||||||
|
setHighlightColor(color: string) {
|
||||||
|
if (color !== currHighlightColor) {
|
||||||
|
currHighlightColor = color;
|
||||||
|
callbacks.forEach((cb) => cb(currentFilter, currentFilter));
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,21 +104,32 @@ export const HighlightContext = createContext<HighlightManager>({
|
|||||||
// stub implementation in case we render a component without a Highlight context
|
// stub implementation in case we render a component without a Highlight context
|
||||||
return text;
|
return text;
|
||||||
},
|
},
|
||||||
|
setHighlightColor(_color: string) {
|
||||||
|
throw new Error('Cannot set the color of a stub highlight manager');
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export function HighlightProvider({
|
export function HighlightProvider({
|
||||||
text,
|
text,
|
||||||
|
highlightColor,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
text: string | undefined;
|
text: string | undefined;
|
||||||
|
highlightColor?: string | undefined;
|
||||||
children: React.ReactElement;
|
children: React.ReactElement;
|
||||||
}) {
|
}) {
|
||||||
const [highlightManager] = useState(() => createHighlightManager(text));
|
const [highlightManager] = useState(() =>
|
||||||
|
createHighlightManager(text, highlightColor),
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
highlightManager.setFilter(text);
|
highlightManager.setFilter(text);
|
||||||
}, [text, highlightManager]);
|
}, [text, highlightManager]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
highlightManager.setHighlightColor(highlightColor);
|
||||||
|
}, [highlightColor, highlightManager]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HighlightContext.Provider value={highlightManager}>
|
<HighlightContext.Provider value={highlightManager}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -115,9 +115,7 @@ test.local('can filter for data', async () => {
|
|||||||
expect(element.parentElement).toMatchInlineSnapshot(`
|
expect(element.parentElement).toMatchInlineSnapshot(`
|
||||||
<span>
|
<span>
|
||||||
"j
|
"j
|
||||||
<span
|
<span>
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
|
||||||
>
|
|
||||||
son
|
son
|
||||||
</span>
|
</span>
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -609,6 +609,10 @@ export function DataTable<T extends object>(
|
|||||||
tableState.highlightSearchSetting.highlightEnabled
|
tableState.highlightSearchSetting.highlightEnabled
|
||||||
? tableState.searchValue
|
? tableState.searchValue
|
||||||
: ''
|
: ''
|
||||||
|
}
|
||||||
|
highlightColor={
|
||||||
|
tableState.highlightSearchSetting.color ||
|
||||||
|
theme.searchHighlightBackground.yellow
|
||||||
}>
|
}>
|
||||||
{mainSection}
|
{mainSection}
|
||||||
</HighlightProvider>
|
</HighlightProvider>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {Percentage} from '../../utils/widthUtils';
|
|||||||
import {MutableRefObject, Reducer} from 'react';
|
import {MutableRefObject, Reducer} from 'react';
|
||||||
import {DataSource, DataSourceVirtualizer} from '../../data-source/index';
|
import {DataSource, DataSourceVirtualizer} from '../../data-source/index';
|
||||||
import produce, {castDraft, immerable, original} from 'immer';
|
import produce, {castDraft, immerable, original} from 'immer';
|
||||||
|
import {theme} from '../theme';
|
||||||
|
|
||||||
export type OnColumnResize = (id: string, size: number | Percentage) => void;
|
export type OnColumnResize = (id: string, size: number | Percentage) => void;
|
||||||
export type Sorting<T = any> = {
|
export type Sorting<T = any> = {
|
||||||
@@ -106,7 +107,8 @@ type DataManagerActions<T> =
|
|||||||
| Action<'setAutoScroll', {autoScroll: boolean}>
|
| Action<'setAutoScroll', {autoScroll: boolean}>
|
||||||
| Action<'toggleSearchValue'>
|
| Action<'toggleSearchValue'>
|
||||||
| Action<'clearSearchHistory'>
|
| Action<'clearSearchHistory'>
|
||||||
| Action<'toggleHighlightSearch'>;
|
| Action<'toggleHighlightSearch'>
|
||||||
|
| Action<'setSearchHighlightColor', {color: string}>;
|
||||||
|
|
||||||
type DataManagerConfig<T> = {
|
type DataManagerConfig<T> = {
|
||||||
dataSource: DataSource<T, T[keyof T]>;
|
dataSource: DataSource<T, T[keyof T]>;
|
||||||
@@ -309,6 +311,12 @@ export const dataTableManagerReducer = produce<
|
|||||||
!draft.highlightSearchSetting.highlightEnabled;
|
!draft.highlightSearchSetting.highlightEnabled;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'setSearchHighlightColor': {
|
||||||
|
if (draft.highlightSearchSetting.color !== action.color) {
|
||||||
|
draft.highlightSearchSetting.color = action.color;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new Error('Unknown action ' + (action as any).type);
|
throw new Error('Unknown action ' + (action as any).type);
|
||||||
}
|
}
|
||||||
@@ -341,6 +349,7 @@ export type DataTableManager<T> = {
|
|||||||
dataSource: DataSource<T, T[keyof T]>;
|
dataSource: DataSource<T, T[keyof T]>;
|
||||||
toggleSearchValue(): void;
|
toggleSearchValue(): void;
|
||||||
toggleHighlightSearch(): void;
|
toggleHighlightSearch(): void;
|
||||||
|
setSearchHighlightColor(color: string): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createDataTableManager<T>(
|
export function createDataTableManager<T>(
|
||||||
@@ -393,6 +402,9 @@ export function createDataTableManager<T>(
|
|||||||
toggleHighlightSearch() {
|
toggleHighlightSearch() {
|
||||||
dispatch({type: 'toggleHighlightSearch'});
|
dispatch({type: 'toggleHighlightSearch'});
|
||||||
},
|
},
|
||||||
|
setSearchHighlightColor(color) {
|
||||||
|
dispatch({type: 'setSearchHighlightColor', color});
|
||||||
|
},
|
||||||
dataSource,
|
dataSource,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -440,7 +452,7 @@ export function createInitialState<T>(
|
|||||||
autoScroll: prefs?.autoScroll ?? config.autoScroll ?? false,
|
autoScroll: prefs?.autoScroll ?? config.autoScroll ?? false,
|
||||||
highlightSearchSetting: prefs?.highlightSearchSetting ?? {
|
highlightSearchSetting: prefs?.highlightSearchSetting ?? {
|
||||||
highlightEnabled: false,
|
highlightEnabled: false,
|
||||||
color: '',
|
color: theme.searchHighlightBackground.yellow,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {CopyOutlined, FilterOutlined, TableOutlined} from '@ant-design/icons';
|
import {CopyOutlined, FilterOutlined, TableOutlined} from '@ant-design/icons';
|
||||||
import {Checkbox, Menu, Switch} from 'antd';
|
import {Badge, Checkbox, Menu, Select, Switch} from 'antd';
|
||||||
import {Layout} from 'flipper-plugin';
|
import {Layout} from 'flipper-plugin';
|
||||||
import {
|
import {
|
||||||
DataTableDispatch,
|
DataTableDispatch,
|
||||||
@@ -25,8 +25,10 @@ import {toFirstUpper} from '../../utils/toFirstUpper';
|
|||||||
import {DataSource} from '../../data-source/index';
|
import {DataSource} from '../../data-source/index';
|
||||||
import {renderColumnValue} from './TableRow';
|
import {renderColumnValue} from './TableRow';
|
||||||
import {textContent} from '../../utils/textContent';
|
import {textContent} from '../../utils/textContent';
|
||||||
|
import {theme} from '../theme';
|
||||||
|
|
||||||
const {Item, SubMenu} = Menu;
|
const {Item, SubMenu} = Menu;
|
||||||
|
const {Option} = Select;
|
||||||
|
|
||||||
export function tableContextMenuFactory<T>(
|
export function tableContextMenuFactory<T>(
|
||||||
datasource: DataSource<T, T[keyof T]>,
|
datasource: DataSource<T, T[keyof T]>,
|
||||||
@@ -197,6 +199,7 @@ export function tableContextMenuFactory<T>(
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}}>
|
}}>
|
||||||
|
Highlight search terms
|
||||||
<Switch
|
<Switch
|
||||||
checked={highlightSearchSetting.highlightEnabled}
|
checked={highlightSearchSetting.highlightEnabled}
|
||||||
size="small"
|
size="small"
|
||||||
@@ -206,7 +209,42 @@ export function tableContextMenuFactory<T>(
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
Highlight search terms
|
</Layout.Horizontal>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="highlight search color">
|
||||||
|
<Layout.Horizontal
|
||||||
|
gap
|
||||||
|
center
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}}>
|
||||||
|
Highlight search color
|
||||||
|
<Select
|
||||||
|
style={{width: '7em'}}
|
||||||
|
defaultValue={highlightSearchSetting.color}
|
||||||
|
onChange={(color: string) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'setSearchHighlightColor',
|
||||||
|
color: color,
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
{Object.entries(theme.searchHighlightBackground).map(
|
||||||
|
([colorName, color]) => (
|
||||||
|
<Option key={colorName} value={color}>
|
||||||
|
<Badge
|
||||||
|
text={
|
||||||
|
<span style={{backgroundColor: color}}>
|
||||||
|
{colorName.charAt(0).toUpperCase() +
|
||||||
|
colorName.slice(1)}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
color={color}
|
||||||
|
/>
|
||||||
|
</Option>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</SubMenu>
|
</SubMenu>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ test('update and append', async () => {
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
style="background-color: rgb(255, 245, 102);"
|
||||||
/>
|
/>
|
||||||
test DataTable
|
test DataTable
|
||||||
</span>
|
</span>
|
||||||
@@ -70,7 +70,7 @@ test('update and append', async () => {
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
style="background-color: rgb(255, 245, 102);"
|
||||||
/>
|
/>
|
||||||
true
|
true
|
||||||
</span>
|
</span>
|
||||||
@@ -123,7 +123,7 @@ test('column visibility', async () => {
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
style="background-color: rgb(255, 245, 102);"
|
||||||
/>
|
/>
|
||||||
test DataTable
|
test DataTable
|
||||||
</span>
|
</span>
|
||||||
@@ -134,7 +134,7 @@ test('column visibility', async () => {
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
style="background-color: rgb(255, 245, 102);"
|
||||||
/>
|
/>
|
||||||
true
|
true
|
||||||
</span>
|
</span>
|
||||||
@@ -160,7 +160,7 @@ test('column visibility', async () => {
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
style="background-color: rgb(255, 245, 102);"
|
||||||
/>
|
/>
|
||||||
test DataTable
|
test DataTable
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ test('update and append', async () => {
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
style="background-color: rgb(255, 245, 102);"
|
||||||
/>
|
/>
|
||||||
test DataTable
|
test DataTable
|
||||||
</span>
|
</span>
|
||||||
@@ -63,7 +63,7 @@ test('update and append', async () => {
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
style="background-color: rgb(255, 245, 102);"
|
||||||
/>
|
/>
|
||||||
true
|
true
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class PartialHighlight extends PureComponent<{
|
|||||||
content: string;
|
content: string;
|
||||||
}> {
|
}> {
|
||||||
static HighlightedText = styled.span<{selected: boolean}>((props) => ({
|
static HighlightedText = styled.span<{selected: boolean}>((props) => ({
|
||||||
backgroundColor: theme.searchHighlightBackground,
|
backgroundColor: theme.searchHighlightBackground.yellow,
|
||||||
color: props.selected ? `${theme.textColorPrimary} !important` : 'auto',
|
color: props.selected ? `${theme.textColorPrimary} !important` : 'auto',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,12 @@ export const theme = {
|
|||||||
textColorSecondary: 'var(--flipper-text-color-secondary)',
|
textColorSecondary: 'var(--flipper-text-color-secondary)',
|
||||||
textColorPlaceholder: 'var(--flipper-text-color-placeholder)',
|
textColorPlaceholder: 'var(--flipper-text-color-placeholder)',
|
||||||
textColorActive: 'var(--light-color-button-active)',
|
textColorActive: 'var(--light-color-button-active)',
|
||||||
searchHighlightBackground: antColors.yellow[3],
|
searchHighlightBackground: {
|
||||||
|
yellow: antColors.yellow[3],
|
||||||
|
red: antColors.red[3],
|
||||||
|
green: antColors.green[3],
|
||||||
|
blue: antColors.blue[3],
|
||||||
|
} as const,
|
||||||
selectionBackgroundColor: 'var(--flipper-primary-background-wash)',
|
selectionBackgroundColor: 'var(--flipper-primary-background-wash)',
|
||||||
disabledColor: 'var(--flipper-disabled-color)',
|
disabledColor: 'var(--flipper-disabled-color)',
|
||||||
backgroundDefault: 'var(--flipper-background-default)',
|
backgroundDefault: 'var(--flipper-background-default)',
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ test('It can render rows', async () => {
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
style="background-color: rgb(255, 245, 102);"
|
||||||
/>
|
/>
|
||||||
00:00:00.000
|
00:00:00.000
|
||||||
</span>
|
</span>
|
||||||
@@ -140,7 +140,7 @@ test('It can render rows', async () => {
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
style="background-color: rgb(255, 245, 102);"
|
||||||
/>
|
/>
|
||||||
Android Phone
|
Android Phone
|
||||||
</span>
|
</span>
|
||||||
@@ -151,7 +151,7 @@ test('It can render rows', async () => {
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
style="background-color: rgb(255, 245, 102);"
|
||||||
/>
|
/>
|
||||||
FB4A
|
FB4A
|
||||||
</span>
|
</span>
|
||||||
@@ -162,7 +162,7 @@ test('It can render rows', async () => {
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
style="background-color: rgb(255, 245, 102);"
|
||||||
/>
|
/>
|
||||||
unique-string
|
unique-string
|
||||||
</span>
|
</span>
|
||||||
@@ -173,7 +173,7 @@ test('It can render rows', async () => {
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
style="background-color: rgb(255, 245, 102);"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -183,7 +183,7 @@ test('It can render rows', async () => {
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
style="background-color: rgb(255, 245, 102);"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -193,7 +193,7 @@ test('It can render rows', async () => {
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
style="background-color: rgb(255, 245, 102);"
|
||||||
/>
|
/>
|
||||||
toClient:send
|
toClient:send
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user