Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added generic and SearchBar and Orama powered SearchBar. #5419

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
695 changes: 695 additions & 0 deletions components/Common/OramaSearchBar/OramaLogo.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Common/OramaSearchBar Default smoke-test 1`] = `
<div class="SearchBar_searchBarContainer__Bo5vz"
style="width: 0px; height: 3em;"
>
<div class="SearchBar_searchInputContainer__Imb5N"
role="presentation"
>
<svg stroke="currentColor"
fill="currentColor"
stroke-width="0"
viewbox="0 0 24 24"
class="SearchBar_searchIcon__nTZMm"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path fill="none"
d="M0 0h24v24H0z"
>
</path>
<path d="M19.3 16.9c.4-.7.7-1.5.7-2.4 0-2.5-2-4.5-4.5-4.5S11 12 11 14.5s2 4.5 4.5 4.5c.9 0 1.7-.3 2.4-.7l3.2 3.2 1.4-1.4-3.2-3.2zm-3.8.1c-1.4 0-2.5-1.1-2.5-2.5s1.1-2.5 2.5-2.5 2.5 1.1 2.5 2.5-1.1 2.5-2.5 2.5zM12 20v2C6.48 22 2 17.52 2 12S6.48 2 12 2c4.84 0 8.87 3.44 9.8 8h-2.07A8 8 0 0015 4.59V5c0 1.1-.9 2-2 2h-2v2c0 .55-.45 1-1 1H8v2h2v3H9l-4.79-4.79C4.08 10.79 4 11.38 4 12c0 4.41 3.59 8 8 8z">
</path>
</svg>
<label class="SearchBar_searchLabel__d_ELb"
for="searchInput"
>
<span>
Search
</span>
<input autocomplete="off"
class="SearchBar_searchInput__YjZjD"
id="searchInput"
name="query"
type="text"
value
>
</label>
</div>
</div>
`;
24 changes: 24 additions & 0 deletions components/Common/OramaSearchBar/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.oramaSearchBarFooter {
ShogunPanda marked this conversation as resolved.
Show resolved Hide resolved
align-items: center;
column-gap: 1ch;
display: flex;
justify-content: end;
margin: 5px;

.oramaSearchBarFooterLabel {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use &_ statements here, so you don't need to add the whole prefix again. (Actually in all the child Sass styles here)

Eg.:

.oramaSearchBarFooter {

  &_label {

  }

}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, I'll update this

color: var(--black6);
font-size: 0.8em;
margin-top: -2px;
white-space: nowrap;
}

.oramaSearchBarFooterLink {
align-items: center;
display: flex;
justify-content: center;
}

.oramaSearchBarFooterLogo {
min-width: 6em;
}
}
50 changes: 50 additions & 0 deletions components/Common/OramaSearchBar/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { create, insertMultiple, save } from '@orama/orama';
import OramaSearchBar from './index';

import type { Meta as MetaObj, StoryObj } from '@storybook/react';
import type { Schema } from '@orama/orama';

type Story = StoryObj<typeof OramaSearchBar>;
type Meta = MetaObj<typeof OramaSearchBar>;

const schema: Schema = { title: 'string', displayTitle: 'string' };
const documents = [
{
id: 'voluptate',
slug: 'voluptate',
title: 'Nisi deserunt excepteur',
category: 'aliqua-sunt',
displayTitle: 'Adipisicing magna irure elit velit.',
},
{
id: 'ullamco',
slug: 'ullamco',
title: 'Lorem adipisicing ut',
category: 'excepteur',
displayTitle: 'Aliqua voluptate aliqua non consectetur sunt consequat.',
},
{
id: 'deserunt',
slug: 'deserunt',
title: 'Qui irure do irure',
category: 'laborum',
wrapInCode: true,
},
];

export const Default: Story = {
loaders: [
async () => {
const database = await create({ schema });
await insertMultiple(database, documents);
return { orama: await save(database) };
},
],
};

export default {
component: OramaSearchBar,
render: (args, { loaded: { orama } }) => {
return <OramaSearchBar schema={schema} index={orama} />;
},
} as Meta;
64 changes: 64 additions & 0 deletions components/Common/OramaSearchBar/index.tsx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all the application logic, like performing search and etc should be done in a Hook and utils files.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. What about now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me re-review 👀

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { create, load } from '@orama/orama';
import { useTheme } from 'next-themes';
import { useCallback, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { OramaLogoDark, OramaLogoLight } from './OramaLogo';
import styles from './index.module.scss';
ShogunPanda marked this conversation as resolved.
Show resolved Hide resolved
import { identityDocumentMapper, performSearch } from './search';
import SearchBar from '../SearchBar';

import type { FC } from 'react';
import type { OramaSearchBarProps } from './search';
import type { SearchFunction } from '../../../types';

const Footer: FC = () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this become a separate component OramaSearchBarFooter.tsx? Just because our approach here is to have one top-level Component per file.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess so, it's pretty self contained. Will move it soon.

const { resolvedTheme: theme } = useTheme();

const isDark = theme === 'dark';

return (
ShogunPanda marked this conversation as resolved.
Show resolved Hide resolved
<div className={styles.oramaSearchBarFooter}>
<span className={styles.oramaSearchBarFooterLabel}>
<FormattedMessage id="components.oramaSearchBar.search.poweredBy" />
</span>
<a
href="http://oramajs.io"
target="_blank"
rel="noopener noreferrer"
className={styles.oramaSearchBarFooterLink}
>
{isDark ? (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can simplify this to:

const OramaLogo = theme === 'dark' ? OramaLogoDark : OramaLogoLight;

...

<OramaLogo className={styles.oramaSearchBarFooterLogo} />

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

<OramaLogoDark className={styles.oramaSearchBarFooterLogo} />
) : (
<OramaLogoLight className={styles.oramaSearchBarFooterLogo} />
)}
</a>
</div>
);
};

const OramaSearchBar: FC<OramaSearchBarProps> = ({
schema,
index,
documentMapper,
}: OramaSearchBarProps) => {
const [searchFunction, setSearchFunction] = useState<SearchFunction>();

// Create a setup function to deserialize the database
const setup = useCallback(async () => {
ShogunPanda marked this conversation as resolved.
Show resolved Hide resolved
const database = await create({ schema });
await load(database, index);

setSearchFunction(() =>
performSearch.bind(
null,
database,
documentMapper ?? identityDocumentMapper
)
);
}, [schema, index, documentMapper, setSearchFunction]);

return <SearchBar setup={setup} search={searchFunction} footer={Footer} />;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume that the SearchBar awaits for the setup function?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, see on line 157.

};

export default OramaSearchBar;
26 changes: 26 additions & 0 deletions components/Common/OramaSearchBar/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { search } from '@orama/orama';

import type { Document, Orama, RawData, Schema } from '@orama/orama';
import type { SearchResult } from '../../../types';

export type DocumentMapper = (doc: Document) => SearchResult;

export type OramaSearchBarProps = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe types should be solely defined on a types.ts file just to separate type definitions from actual search logic

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue. Will move them.

schema: Schema;
index: RawData;
documentMapper?: DocumentMapper;
};

export const identityDocumentMapper = (doc: Document): SearchResult => {
ShogunPanda marked this conversation as resolved.
Show resolved Hide resolved
return doc as unknown as SearchResult;
};

export const performSearch = async (
database: Orama,
mapper: DocumentMapper,
term: string
): Promise<SearchResult[]> => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel that this could be transformed into a promise. Could we do that?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ehm... it is already a promise. What do you mean exactly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You’re mixing async with Promises. I’m referring to use a pure Promise approach for this function

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ehm... Promise it's just the return type. Are looking for something like this?

export const performSearch = (
  database: Orama,
  mapper: DocumentMapper,
  term: string
): Promise<SearchResult[]> => {
  return search(database, { term }).then(results => results.hits.map(h => mapper(h.document)));
};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, that’s what I meant. I also guess we don’t need a return statement as this is an one liner.

Yeah, I meant native promises rather than async/await.

const results = await search(database, { term });

return results.hits.map(h => mapper(h.document));
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Common/SearchBar Default smoke-test 1`] = `
<div class="SearchBar_searchBarContainer__Bo5vz"
style="width: 0px; height: 3em;"
>
<div class="SearchBar_searchInputContainer__Imb5N"
role="presentation"
>
<svg stroke="currentColor"
fill="currentColor"
stroke-width="0"
viewbox="0 0 24 24"
class="SearchBar_searchIcon__nTZMm"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path fill="none"
d="M0 0h24v24H0z"
>
</path>
<path d="M19.3 16.9c.4-.7.7-1.5.7-2.4 0-2.5-2-4.5-4.5-4.5S11 12 11 14.5s2 4.5 4.5 4.5c.9 0 1.7-.3 2.4-.7l3.2 3.2 1.4-1.4-3.2-3.2zm-3.8.1c-1.4 0-2.5-1.1-2.5-2.5s1.1-2.5 2.5-2.5 2.5 1.1 2.5 2.5-1.1 2.5-2.5 2.5zM12 20v2C6.48 22 2 17.52 2 12S6.48 2 12 2c4.84 0 8.87 3.44 9.8 8h-2.07A8 8 0 0015 4.59V5c0 1.1-.9 2-2 2h-2v2c0 .55-.45 1-1 1H8v2h2v3H9l-4.79-4.79C4.08 10.79 4 11.38 4 12c0 4.41 3.59 8 8 8z">
</path>
</svg>
<label class="SearchBar_searchLabel__d_ELb"
for="searchInput"
>
<span>
Search
</span>
<input autocomplete="off"
class="SearchBar_searchInput__YjZjD"
id="searchInput"
name="query"
type="text"
value
>
</label>
</div>
</div>
`;
136 changes: 136 additions & 0 deletions components/Common/SearchBar/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
.searchBarContainer {
background-color: var(--color-fill-top-nav);
border-radius: 6px;
box-shadow: none;
display: flex;
flex-direction: column;
max-height: 50vh;
max-width: 450px;
min-height: min-content;
min-width: min-content;
overflow-y: hidden;

&.expanded {
box-shadow: 0px 2px 12px 3px rgba(153, 204, 125, 0.14);
}

.searchInputContainer {
align-items: center;
display: flex;
position: relative;
width: 100%;

.searchIcon,
.closeIcon {
color: var(--color-text-accent);
padding: 1rem;
vertical-align: middle;
}

.closeIcon {
padding: 0 1rem 0 0;
}

.searchLabel {
align-items: center;
color: var(--color-text-accent);
cursor: pointer;
display: flex;
flex: 1;
font-weight: var(--font-weight-semibold);
transition: all 250ms ease-in-out;
width: 100%;
}

.searchInput {
background-color: transparent;
border: none;
border-radius: 6px;
color: var(--color-text-primary);
font-size: var(--font-size-body1);
font-weight: var(--font-weight-semibold);
height: 100%;
outline: none;
width: 100%;

&:focus {
outline: none;
}
}
}

.searchResults {
display: flex;
flex-direction: column;
height: 100%;
min-height: 5em;
overflow-y: auto;
width: 100%;

&.noResults {
justify-content: center;
}

.loadingMessage {
align-self: center;
color: var(--color-text-primary);
display: flex;
font-size: 14px;
justify-content: center;
justify-self: center;
white-space: nowrap;
}
}

.searchResultsList {
list-style: circle;
margin: 0;
padding: 0 0 0 var(--space-32);
}

.searchResultsFooter {
border-top: var(--space-01) solid var(--black2);
}

.searchResult {
color: var(--color-text-primary);
display: list-item;
margin: 0;
padding: 3px;

&:hover {
font-weight: bold;
}

.searchResultLink {
display: block;
padding: 2px;
text-decoration: none;
text-overflow: ellipsis;

&:focus {
outline: var(--brand3) dotted 2px;
}
}

.searchResultText {
text-transform: capitalize;
}

.searchResultCode {
border-radius: 0.3rem;
font-weight: var(--font-weight-light);
padding: 0 6px;
}
}
}

[data-theme='light'] .searchResultCode {
shanpriyan marked this conversation as resolved.
Show resolved Hide resolved
background-color: var(--black2);
color: var(--black9);
}

[data-theme='dark'] .searchResultCode {
background-color: var(--black9);
color: var(--black2);
}
Loading