Skip to main content

Unified Search

<UnifiedSearch /> is a component to display a search input with a dropdown for results.

Search sections

One or many available search sections (providers) can be specified:

Section componentDescription
<SearchSectionElementTypes />Allows to lookup entity types displayed in the tree form and create new entities in authoring mode.
<SearchSectionEntities />Allows to lookup entities using data provider.
<SearchSectionLinkTypes />Allows to lookup displayed link types and change their visibility settings.

Implement a custom search section

useUnifiedSearchSection() hook can be used to implement a custom search section:

Result
Loading...
Live Editor
function NpmPackagesSearchSection() {
  const {shouldRender, searchStore} = Reactodia.useUnifiedSearchSection();

  const [items, setItems] = React.useState<readonly Reactodia.ElementModel[]>([]);
  const [selection, setSelection] = React.useState(() => new Set<Reactodia.ElementIri>());

  React.useEffect(() => {
    const listener = new Reactodia.EventObserver();
    let controller = new AbortController();
    listener.listen(searchStore.events, 'executeSearch', async ({value}) => {
      controller.abort();
      controller = new AbortController();
      const signal = controller.signal;
      let items: Reactodia.ElementModel[] = [];
      try {
        items = await queryNpmRegistry(value, signal);
      } catch (err) {
        if (signal.aborted) { return; }
        console.error(err);
      }
      setItems(items);
      setSelection(new Set<Reactodia.ElementIri>());
    });
    listener.listen(searchStore.events, 'clearSearch', ({value}) => {
      controller.abort();
      setItems([]);
      setSelection(new Set<Reactodia.ElementIri>());
    });
    return () => {
      controller.abort();
      listener.stopListening();
    };
  }, [searchStore]);

  return shouldRender ? (
    <div className='reactodia-scrollable'>
      <Reactodia.SearchResults
        items={items}
        selection={selection}
        onSelectionChanged={setSelection}
      />
    </div>
  ) : null;
}

async function queryNpmRegistry(
  text: string,
  signal: AbortSignal
): Reactodia.ElementModel[] {
  if (text.length === 0) {
    return [];
  }
  const res = await fetch(
    `https://registry.npmjs.org/-/v1/search?text=${text}`,
    {signal}
  );
  const data = await res.json();
  return data.objects.map((entry): Reactodia.ElementModel => ({
    id: `https://registry.npmjs.org/${entry.package.name}`,
    types: [`urn:npmjs:Package`],
    label: [],
    properties: {},
  }));
}

function SearchWithNpm() {
  const {defaultLayout} = Reactodia.useWorker(Layouts);
  const sections = React.useMemo(() => [
    {
      key: 'npmPackages',
      label: 'NPM packages',
      component: <NpmPackagesSearchSection />
    },
  ], []);

  return (
    <div className='reactodia-live-editor'>
      <Reactodia.Workspace defaultLayout={defaultLayout}>
        <Reactodia.DefaultWorkspace
          search={{sections}}
        />
      </Reactodia.Workspace>
    </div>
  );
}

render(<SearchWithNpm />);