Move highlighter to flipper-plugin
Summary: This stack moves the DataInspector component to flipper-plugin. The first diffs will move some utility abstractions to turn this into smaller steps Reviewed By: passy Differential Revision: D27591890 fbshipit-source-id: d0285ca31b6c9b334000dd15c722b9bda7638d73
This commit is contained in:
committed by
Facebook GitHub Bot
parent
cd27e4c010
commit
b6674bf96b
@@ -85,6 +85,13 @@ export {createDataSource, DataSource} from './state/DataSource';
|
||||
export {DataTable, DataTableColumn} from './ui/data-table/DataTable';
|
||||
export {DataTableManager} from './ui/data-table/DataTableManager';
|
||||
|
||||
export {
|
||||
HighlightContext as _HighlightContext,
|
||||
HighlightProvider as _HighlightProvider,
|
||||
HighlightManager as _HighlightManager,
|
||||
useHighlighter as _useHighlighter,
|
||||
} from './ui/Highlight';
|
||||
|
||||
export {
|
||||
Interactive as _Interactive,
|
||||
InteractiveProps as _InteractiveProps,
|
||||
|
||||
125
desktop/flipper-plugin/src/ui/Highlight.tsx
Normal file
125
desktop/flipper-plugin/src/ui/Highlight.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
import React, {
|
||||
useEffect,
|
||||
memo,
|
||||
useState,
|
||||
useRef,
|
||||
createContext,
|
||||
useContext,
|
||||
} from 'react';
|
||||
import {debounce} from 'lodash';
|
||||
import {theme} from './theme';
|
||||
|
||||
const Highlighted = styled.span({
|
||||
backgroundColor: theme.textColorActive,
|
||||
});
|
||||
|
||||
export interface HighlightManager {
|
||||
setFilter(text: string | undefined): void;
|
||||
render(text: string): React.ReactNode;
|
||||
}
|
||||
|
||||
function createHighlightManager(initialText: string = ''): HighlightManager {
|
||||
const callbacks = new Set<(prev: string, next: string) => void>();
|
||||
let matches = 0;
|
||||
let currentFilter = initialText;
|
||||
|
||||
const Highlight: React.FC<{text: string}> = memo(({text}) => {
|
||||
const [, setUpdate] = useState(0);
|
||||
const elem = useRef<HTMLSpanElement | null>(null);
|
||||
useEffect(() => {
|
||||
function onChange(prevHighlight: string, newHighlight: string) {
|
||||
const prevIndex = text.toLowerCase().indexOf(prevHighlight);
|
||||
const newIndex = text.toLowerCase().indexOf(newHighlight);
|
||||
if (prevIndex !== newIndex || newIndex !== -1) {
|
||||
// either we had a result, and we have no longer,
|
||||
// or we still have a result, but the highlightable text changed
|
||||
if (newIndex !== -1) {
|
||||
if (++matches === 1) {
|
||||
elem.current?.parentElement?.parentElement?.scrollIntoView?.();
|
||||
}
|
||||
}
|
||||
setUpdate((s) => s + 1);
|
||||
}
|
||||
}
|
||||
callbacks.add(onChange);
|
||||
return () => {
|
||||
callbacks.delete(onChange);
|
||||
};
|
||||
}, [text]);
|
||||
|
||||
const index = text.toLowerCase().indexOf(currentFilter);
|
||||
return (
|
||||
<span ref={elem}>
|
||||
{index === -1 ? (
|
||||
text
|
||||
) : (
|
||||
<>
|
||||
{text.substr(0, index)}
|
||||
<Highlighted>
|
||||
{text.substr(index, currentFilter.length)}
|
||||
</Highlighted>
|
||||
{text.substr(index + currentFilter.length)}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
setFilter: debounce((text: string = '') => {
|
||||
if (currentFilter !== text) {
|
||||
matches = 0;
|
||||
const base = currentFilter;
|
||||
currentFilter = text.toLowerCase();
|
||||
callbacks.forEach((cb) => cb(base, currentFilter));
|
||||
}
|
||||
}, 100),
|
||||
render(text: string) {
|
||||
return <Highlight text={text} />;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const HighlightContext = createContext<HighlightManager>({
|
||||
setFilter(_text: string) {
|
||||
throw new Error('Cannot set the filter of a stub highlight manager');
|
||||
},
|
||||
render(text: string) {
|
||||
// stub implementation in case we render a component without a Highlight context
|
||||
return text;
|
||||
},
|
||||
});
|
||||
|
||||
export function HighlightProvider({
|
||||
text,
|
||||
children,
|
||||
}: {
|
||||
text: string | undefined;
|
||||
children: React.ReactElement;
|
||||
}) {
|
||||
const [highlightManager] = useState(() => createHighlightManager(text));
|
||||
|
||||
useEffect(() => {
|
||||
highlightManager.setFilter(text);
|
||||
}, [text, highlightManager]);
|
||||
|
||||
return (
|
||||
<HighlightContext.Provider value={highlightManager}>
|
||||
{children}
|
||||
</HighlightContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useHighlighter(): HighlightManager {
|
||||
return useContext(HighlightContext);
|
||||
}
|
||||
Reference in New Issue
Block a user