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
|
||||
*/
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
import React, {
|
||||
useEffect,
|
||||
memo,
|
||||
@@ -19,19 +18,20 @@ import React, {
|
||||
import {debounce} from 'lodash';
|
||||
import {theme} from './theme';
|
||||
|
||||
const Highlighted = styled.span({
|
||||
backgroundColor: theme.searchHighlightBackground,
|
||||
});
|
||||
|
||||
export interface HighlightManager {
|
||||
setFilter(text: string | undefined): void;
|
||||
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>();
|
||||
let matches = 0;
|
||||
let currentFilter = initialText;
|
||||
let currHighlightColor = initialHighlightColor;
|
||||
|
||||
const Highlight: React.FC<{text: string}> = memo(({text}) => {
|
||||
const [, setUpdate] = useState(0);
|
||||
@@ -65,9 +65,9 @@ function createHighlightManager(initialText: string = ''): HighlightManager {
|
||||
) : (
|
||||
<>
|
||||
{text.substr(0, index)}
|
||||
<Highlighted>
|
||||
<span style={{backgroundColor: currHighlightColor}}>
|
||||
{text.substr(index, currentFilter.length)}
|
||||
</Highlighted>
|
||||
</span>
|
||||
{text.substr(index + currentFilter.length)}
|
||||
</>
|
||||
)}
|
||||
@@ -87,6 +87,12 @@ function createHighlightManager(initialText: string = ''): HighlightManager {
|
||||
render(text: string) {
|
||||
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
|
||||
return text;
|
||||
},
|
||||
setHighlightColor(_color: string) {
|
||||
throw new Error('Cannot set the color of a stub highlight manager');
|
||||
},
|
||||
});
|
||||
|
||||
export function HighlightProvider({
|
||||
text,
|
||||
highlightColor,
|
||||
children,
|
||||
}: {
|
||||
text: string | undefined;
|
||||
highlightColor?: string | undefined;
|
||||
children: React.ReactElement;
|
||||
}) {
|
||||
const [highlightManager] = useState(() => createHighlightManager(text));
|
||||
const [highlightManager] = useState(() =>
|
||||
createHighlightManager(text, highlightColor),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
highlightManager.setFilter(text);
|
||||
}, [text, highlightManager]);
|
||||
|
||||
useEffect(() => {
|
||||
highlightManager.setHighlightColor(highlightColor);
|
||||
}, [highlightColor, highlightManager]);
|
||||
|
||||
return (
|
||||
<HighlightContext.Provider value={highlightManager}>
|
||||
{children}
|
||||
|
||||
@@ -115,9 +115,7 @@ test.local('can filter for data', async () => {
|
||||
expect(element.parentElement).toMatchInlineSnapshot(`
|
||||
<span>
|
||||
"j
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
>
|
||||
<span>
|
||||
son
|
||||
</span>
|
||||
"
|
||||
|
||||
@@ -609,6 +609,10 @@ export function DataTable<T extends object>(
|
||||
tableState.highlightSearchSetting.highlightEnabled
|
||||
? tableState.searchValue
|
||||
: ''
|
||||
}
|
||||
highlightColor={
|
||||
tableState.highlightSearchSetting.color ||
|
||||
theme.searchHighlightBackground.yellow
|
||||
}>
|
||||
{mainSection}
|
||||
</HighlightProvider>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {Percentage} from '../../utils/widthUtils';
|
||||
import {MutableRefObject, Reducer} from 'react';
|
||||
import {DataSource, DataSourceVirtualizer} from '../../data-source/index';
|
||||
import produce, {castDraft, immerable, original} from 'immer';
|
||||
import {theme} from '../theme';
|
||||
|
||||
export type OnColumnResize = (id: string, size: number | Percentage) => void;
|
||||
export type Sorting<T = any> = {
|
||||
@@ -106,7 +107,8 @@ type DataManagerActions<T> =
|
||||
| Action<'setAutoScroll', {autoScroll: boolean}>
|
||||
| Action<'toggleSearchValue'>
|
||||
| Action<'clearSearchHistory'>
|
||||
| Action<'toggleHighlightSearch'>;
|
||||
| Action<'toggleHighlightSearch'>
|
||||
| Action<'setSearchHighlightColor', {color: string}>;
|
||||
|
||||
type DataManagerConfig<T> = {
|
||||
dataSource: DataSource<T, T[keyof T]>;
|
||||
@@ -309,6 +311,12 @@ export const dataTableManagerReducer = produce<
|
||||
!draft.highlightSearchSetting.highlightEnabled;
|
||||
break;
|
||||
}
|
||||
case 'setSearchHighlightColor': {
|
||||
if (draft.highlightSearchSetting.color !== action.color) {
|
||||
draft.highlightSearchSetting.color = action.color;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error('Unknown action ' + (action as any).type);
|
||||
}
|
||||
@@ -341,6 +349,7 @@ export type DataTableManager<T> = {
|
||||
dataSource: DataSource<T, T[keyof T]>;
|
||||
toggleSearchValue(): void;
|
||||
toggleHighlightSearch(): void;
|
||||
setSearchHighlightColor(color: string): void;
|
||||
};
|
||||
|
||||
export function createDataTableManager<T>(
|
||||
@@ -393,6 +402,9 @@ export function createDataTableManager<T>(
|
||||
toggleHighlightSearch() {
|
||||
dispatch({type: 'toggleHighlightSearch'});
|
||||
},
|
||||
setSearchHighlightColor(color) {
|
||||
dispatch({type: 'setSearchHighlightColor', color});
|
||||
},
|
||||
dataSource,
|
||||
};
|
||||
}
|
||||
@@ -440,7 +452,7 @@ export function createInitialState<T>(
|
||||
autoScroll: prefs?.autoScroll ?? config.autoScroll ?? false,
|
||||
highlightSearchSetting: prefs?.highlightSearchSetting ?? {
|
||||
highlightEnabled: false,
|
||||
color: '',
|
||||
color: theme.searchHighlightBackground.yellow,
|
||||
},
|
||||
};
|
||||
// @ts-ignore
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
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 {
|
||||
DataTableDispatch,
|
||||
@@ -25,8 +25,10 @@ import {toFirstUpper} from '../../utils/toFirstUpper';
|
||||
import {DataSource} from '../../data-source/index';
|
||||
import {renderColumnValue} from './TableRow';
|
||||
import {textContent} from '../../utils/textContent';
|
||||
import {theme} from '../theme';
|
||||
|
||||
const {Item, SubMenu} = Menu;
|
||||
const {Option} = Select;
|
||||
|
||||
export function tableContextMenuFactory<T>(
|
||||
datasource: DataSource<T, T[keyof T]>,
|
||||
@@ -197,6 +199,7 @@ export function tableContextMenuFactory<T>(
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}>
|
||||
Highlight search terms
|
||||
<Switch
|
||||
checked={highlightSearchSetting.highlightEnabled}
|
||||
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>
|
||||
</Menu.Item>
|
||||
</SubMenu>
|
||||
|
||||
@@ -59,7 +59,7 @@ test('update and append', async () => {
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
style="background-color: rgb(255, 245, 102);"
|
||||
/>
|
||||
test DataTable
|
||||
</span>
|
||||
@@ -70,7 +70,7 @@ test('update and append', async () => {
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
style="background-color: rgb(255, 245, 102);"
|
||||
/>
|
||||
true
|
||||
</span>
|
||||
@@ -123,7 +123,7 @@ test('column visibility', async () => {
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
style="background-color: rgb(255, 245, 102);"
|
||||
/>
|
||||
test DataTable
|
||||
</span>
|
||||
@@ -134,7 +134,7 @@ test('column visibility', async () => {
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
style="background-color: rgb(255, 245, 102);"
|
||||
/>
|
||||
true
|
||||
</span>
|
||||
@@ -160,7 +160,7 @@ test('column visibility', async () => {
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
style="background-color: rgb(255, 245, 102);"
|
||||
/>
|
||||
test DataTable
|
||||
</span>
|
||||
|
||||
@@ -52,7 +52,7 @@ test('update and append', async () => {
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
style="background-color: rgb(255, 245, 102);"
|
||||
/>
|
||||
test DataTable
|
||||
</span>
|
||||
@@ -63,7 +63,7 @@ test('update and append', async () => {
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
style="background-color: rgb(255, 245, 102);"
|
||||
/>
|
||||
true
|
||||
</span>
|
||||
|
||||
@@ -118,7 +118,7 @@ class PartialHighlight extends PureComponent<{
|
||||
content: string;
|
||||
}> {
|
||||
static HighlightedText = styled.span<{selected: boolean}>((props) => ({
|
||||
backgroundColor: theme.searchHighlightBackground,
|
||||
backgroundColor: theme.searchHighlightBackground.yellow,
|
||||
color: props.selected ? `${theme.textColorPrimary} !important` : 'auto',
|
||||
}));
|
||||
|
||||
|
||||
@@ -21,7 +21,12 @@ export const theme = {
|
||||
textColorSecondary: 'var(--flipper-text-color-secondary)',
|
||||
textColorPlaceholder: 'var(--flipper-text-color-placeholder)',
|
||||
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)',
|
||||
disabledColor: 'var(--flipper-disabled-color)',
|
||||
backgroundDefault: 'var(--flipper-background-default)',
|
||||
|
||||
@@ -129,7 +129,7 @@ test('It can render rows', async () => {
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
style="background-color: rgb(255, 245, 102);"
|
||||
/>
|
||||
00:00:00.000
|
||||
</span>
|
||||
@@ -140,7 +140,7 @@ test('It can render rows', async () => {
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
style="background-color: rgb(255, 245, 102);"
|
||||
/>
|
||||
Android Phone
|
||||
</span>
|
||||
@@ -151,7 +151,7 @@ test('It can render rows', async () => {
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
style="background-color: rgb(255, 245, 102);"
|
||||
/>
|
||||
FB4A
|
||||
</span>
|
||||
@@ -162,7 +162,7 @@ test('It can render rows', async () => {
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
style="background-color: rgb(255, 245, 102);"
|
||||
/>
|
||||
unique-string
|
||||
</span>
|
||||
@@ -173,7 +173,7 @@ test('It can render rows', async () => {
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
style="background-color: rgb(255, 245, 102);"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
@@ -183,7 +183,7 @@ test('It can render rows', async () => {
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
style="background-color: rgb(255, 245, 102);"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
@@ -193,7 +193,7 @@ test('It can render rows', async () => {
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||
style="background-color: rgb(255, 245, 102);"
|
||||
/>
|
||||
toClient:send
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user