From 0f9780a1e52ad8c633dc4b43e30029f81ef264b2 Mon Sep 17 00:00:00 2001 From: Francesca Giannino Date: Wed, 10 Jul 2024 16:30:43 +0200 Subject: [PATCH 1/2] feat: add search results --- apps/storybook/.storybook/preview.js | 13 +++++ apps/storybook/stories/orama-chat.stories.tsx | 2 +- .../stories/orama-search-results.stories.tsx | 18 +++++++ apps/storybook/stories/search-box.stories.tsx | 4 +- .../src/lib/stencil-generated/components.ts | 26 ++++++++- .../src/lib/stencil-generated/index.ts | 1 + .../src/components/stencil-generated/index.ts | 1 + packages/ui-stencil-vue/lib/components.ts | 6 +++ packages/ui-stencil/src/components.d.ts | 19 +++++++ .../internal/orama-input/orama-input.scss | 4 ++ .../internal/orama-input/orama-input.tsx | 8 +-- .../components/internal/orama-input/readme.md | 1 + .../orama-search-results.scss | 22 ++++++++ .../orama-search-results.tsx | 36 +++++++++++++ .../internal/orama-search-results/readme.md | 35 ++++++++++++ .../internal/orama-search/orama-search.tsx | 53 +++++++++---------- .../internal/orama-search/readme.md | 3 ++ .../internal/orama-text/orama-text.tsx | 17 ++++-- .../components/internal/orama-text/readme.md | 2 + .../search-box-toggler/search-box-toggler.tsx | 4 +- .../src/components/search-box/readme.md | 2 + .../src/components/search-box/search-box.tsx | 10 ++-- .../ui-stencil/src/context/searchContext.ts | 6 ++- .../ui-stencil/src/services/SearchService.ts | 10 ++-- packages/ui-stencil/src/styles/globals.scss | 4 -- 25 files changed, 247 insertions(+), 60 deletions(-) create mode 100644 apps/storybook/stories/orama-search-results.stories.tsx create mode 100644 packages/ui-stencil/src/components/internal/orama-search-results/orama-search-results.scss create mode 100644 packages/ui-stencil/src/components/internal/orama-search-results/orama-search-results.tsx create mode 100644 packages/ui-stencil/src/components/internal/orama-search-results/readme.md diff --git a/apps/storybook/.storybook/preview.js b/apps/storybook/.storybook/preview.js index f2c40bd5..f28de7d8 100644 --- a/apps/storybook/.storybook/preview.js +++ b/apps/storybook/.storybook/preview.js @@ -13,6 +13,19 @@ const preview = { color: /(background|color)$/i, date: /Date$/i } + }, + backgrounds: { + default: 'dark', + values: [ + { + name: 'dark', + value: '#050505' + }, + { + name: 'light', + value: '#fbfbfb' + } + ] } } } diff --git a/apps/storybook/stories/orama-chat.stories.tsx b/apps/storybook/stories/orama-chat.stories.tsx index 5b172d12..567c8ccc 100644 --- a/apps/storybook/stories/orama-chat.stories.tsx +++ b/apps/storybook/stories/orama-chat.stories.tsx @@ -1,7 +1,7 @@ import type { StoryObj, Meta } from '@storybook/web-components' const meta: Meta = { - title: 'Internal/OramaChat', + title: 'Demo Preview/OramaChat', component: 'orama-chat' } diff --git a/apps/storybook/stories/orama-search-results.stories.tsx b/apps/storybook/stories/orama-search-results.stories.tsx new file mode 100644 index 00000000..eb236ee7 --- /dev/null +++ b/apps/storybook/stories/orama-search-results.stories.tsx @@ -0,0 +1,18 @@ +import type { StoryObj, Meta } from "@storybook/html"; + +const meta = { + title: "Internal/Search Results", + component: "orama-search-results", +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const Template = (args) => ` + +`; + +export const Default: Story = { + render: Template, + args: {}, +}; \ No newline at end of file diff --git a/apps/storybook/stories/search-box.stories.tsx b/apps/storybook/stories/search-box.stories.tsx index 91a56b33..1718cc99 100644 --- a/apps/storybook/stories/search-box.stories.tsx +++ b/apps/storybook/stories/search-box.stories.tsx @@ -11,7 +11,7 @@ type Story = StoryObj // More on writing stories with args: https://storybook.js.org/docs/html/writing-stories/args export const SearchBox: Story = { render: () => ` -
+
` } @@ -20,7 +20,7 @@ export const SearchBoxWithToggler: Story = { render: () => `
-
+
` diff --git a/packages/ui-stencil-angular/projects/component-library/src/lib/stencil-generated/components.ts b/packages/ui-stencil-angular/projects/component-library/src/lib/stencil-generated/components.ts index 29cdbfc6..2a54326d 100644 --- a/packages/ui-stencil-angular/projects/component-library/src/lib/stencil-generated/components.ts +++ b/packages/ui-stencil-angular/projects/component-library/src/lib/stencil-generated/components.ts @@ -94,14 +94,14 @@ export declare interface OramaChatUserMessage extends Components.OramaChatUserMe @ProxyCmp({ - inputs: ['defaultValue', 'label', 'labelForScreenReaders', 'name', 'size', 'type'] + inputs: ['defaultValue', 'label', 'labelForScreenReaders', 'name', 'placeholder', 'size', 'type'] }) @Component({ selector: 'orama-input', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['defaultValue', 'label', 'labelForScreenReaders', 'name', 'size', 'type'], + inputs: ['defaultValue', 'label', 'labelForScreenReaders', 'name', 'placeholder', 'size', 'type'], }) export class OramaInput { protected el: HTMLElement; @@ -140,6 +140,28 @@ export class OramaSearch { export declare interface OramaSearch extends Components.OramaSearch {} +@ProxyCmp({ + inputs: ['items'] +}) +@Component({ + selector: 'orama-search-results', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['items'], +}) +export class OramaSearchResults { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + + +export declare interface OramaSearchResults extends Components.OramaSearchResults {} + + @ProxyCmp({ inputs: ['as', 'class', 'styledAs'] }) diff --git a/packages/ui-stencil-angular/projects/component-library/src/lib/stencil-generated/index.ts b/packages/ui-stencil-angular/projects/component-library/src/lib/stencil-generated/index.ts index d45355a0..60c00f4f 100644 --- a/packages/ui-stencil-angular/projects/component-library/src/lib/stencil-generated/index.ts +++ b/packages/ui-stencil-angular/projects/component-library/src/lib/stencil-generated/index.ts @@ -8,6 +8,7 @@ export const DIRECTIVES = [ d.OramaChatUserMessage, d.OramaInput, d.OramaSearch, + d.OramaSearchResults, d.OramaText, d.OramaTextarea, d.SearchBox, diff --git a/packages/ui-stencil-react/src/components/stencil-generated/index.ts b/packages/ui-stencil-react/src/components/stencil-generated/index.ts index 167c9c8f..964d7eec 100644 --- a/packages/ui-stencil-react/src/components/stencil-generated/index.ts +++ b/packages/ui-stencil-react/src/components/stencil-generated/index.ts @@ -13,6 +13,7 @@ export const OramaChatMessagesContainer = /*@__PURE__*/createReactComponent('orama-chat-user-message'); export const OramaInput = /*@__PURE__*/createReactComponent('orama-input'); export const OramaSearch = /*@__PURE__*/createReactComponent('orama-search'); +export const OramaSearchResults = /*@__PURE__*/createReactComponent('orama-search-results'); export const OramaText = /*@__PURE__*/createReactComponent('orama-text'); export const OramaTextarea = /*@__PURE__*/createReactComponent('orama-textarea'); export const SearchBox = /*@__PURE__*/createReactComponent('search-box'); diff --git a/packages/ui-stencil-vue/lib/components.ts b/packages/ui-stencil-vue/lib/components.ts index 10d63b15..4edbaaa4 100644 --- a/packages/ui-stencil-vue/lib/components.ts +++ b/packages/ui-stencil-vue/lib/components.ts @@ -29,6 +29,7 @@ export const OramaInput = /*@__PURE__*/ defineContainer('orama-i 'size', 'label', 'type', + 'placeholder', 'labelForScreenReaders', 'defaultValue', 'oramaInputChanged' @@ -38,6 +39,11 @@ export const OramaInput = /*@__PURE__*/ defineContainer('orama-i export const OramaSearch = /*@__PURE__*/ defineContainer('orama-search', undefined); +export const OramaSearchResults = /*@__PURE__*/ defineContainer('orama-search-results', undefined, [ + 'items' +]); + + export const OramaText = /*@__PURE__*/ defineContainer('orama-text', undefined, [ 'as', 'styledAs', diff --git a/packages/ui-stencil/src/components.d.ts b/packages/ui-stencil/src/components.d.ts index 20df8697..f1887480 100644 --- a/packages/ui-stencil/src/components.d.ts +++ b/packages/ui-stencil/src/components.d.ts @@ -7,9 +7,11 @@ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; import { TChatMessage } from "./context/chatContext"; import { InputProps } from "./components/internal/orama-input/orama-input"; +import { SearchResultsProps } from "./components/internal/orama-search-results/orama-search-results"; import { TextProps } from "./components/internal/orama-text/orama-text"; export { TChatMessage } from "./context/chatContext"; export { InputProps } from "./components/internal/orama-input/orama-input"; +export { SearchResultsProps } from "./components/internal/orama-search-results/orama-search-results"; export { TextProps } from "./components/internal/orama-text/orama-text"; export namespace Components { interface OramaChat { @@ -27,11 +29,15 @@ export namespace Components { "label"?: InputProps['label']; "labelForScreenReaders"?: InputProps['labelForScreenReaders']; "name": InputProps['name']; + "placeholder"?: InputProps['placeholder']; "size"?: InputProps['size']; "type"?: InputProps['type']; } interface OramaSearch { } + interface OramaSearchResults { + "items": SearchResultsProps['items']; + } interface OramaText { /** * it defines the HTML tag to be used @@ -113,6 +119,12 @@ declare global { prototype: HTMLOramaSearchElement; new (): HTMLOramaSearchElement; }; + interface HTMLOramaSearchResultsElement extends Components.OramaSearchResults, HTMLStencilElement { + } + var HTMLOramaSearchResultsElement: { + prototype: HTMLOramaSearchResultsElement; + new (): HTMLOramaSearchResultsElement; + }; interface HTMLOramaTextElement extends Components.OramaText, HTMLStencilElement { } var HTMLOramaTextElement: { @@ -144,6 +156,7 @@ declare global { "orama-chat-user-message": HTMLOramaChatUserMessageElement; "orama-input": HTMLOramaInputElement; "orama-search": HTMLOramaSearchElement; + "orama-search-results": HTMLOramaSearchResultsElement; "orama-text": HTMLOramaTextElement; "orama-textarea": HTMLOramaTextareaElement; "search-box": HTMLSearchBoxElement; @@ -167,11 +180,15 @@ declare namespace LocalJSX { "labelForScreenReaders"?: InputProps['labelForScreenReaders']; "name"?: InputProps['name']; "onOramaInputChanged"?: (event: OramaInputCustomEvent) => void; + "placeholder"?: InputProps['placeholder']; "size"?: InputProps['size']; "type"?: InputProps['type']; } interface OramaSearch { } + interface OramaSearchResults { + "items"?: SearchResultsProps['items']; + } interface OramaText { /** * it defines the HTML tag to be used @@ -207,6 +224,7 @@ declare namespace LocalJSX { "orama-chat-user-message": OramaChatUserMessage; "orama-input": OramaInput; "orama-search": OramaSearch; + "orama-search-results": OramaSearchResults; "orama-text": OramaText; "orama-textarea": OramaTextarea; "search-box": SearchBox; @@ -223,6 +241,7 @@ declare module "@stencil/core" { "orama-chat-user-message": LocalJSX.OramaChatUserMessage & JSXBase.HTMLAttributes; "orama-input": LocalJSX.OramaInput & JSXBase.HTMLAttributes; "orama-search": LocalJSX.OramaSearch & JSXBase.HTMLAttributes; + "orama-search-results": LocalJSX.OramaSearchResults & JSXBase.HTMLAttributes; "orama-text": LocalJSX.OramaText & JSXBase.HTMLAttributes; "orama-textarea": LocalJSX.OramaTextarea & JSXBase.HTMLAttributes; "search-box": LocalJSX.SearchBox & JSXBase.HTMLAttributes; diff --git a/packages/ui-stencil/src/components/internal/orama-input/orama-input.scss b/packages/ui-stencil/src/components/internal/orama-input/orama-input.scss index e0039ccb..5c57d7ab 100644 --- a/packages/ui-stencil/src/components/internal/orama-input/orama-input.scss +++ b/packages/ui-stencil/src/components/internal/orama-input/orama-input.scss @@ -92,3 +92,7 @@ display: inline-flex; } } + +.sr-only { + @include screen-reader-only(); +} diff --git a/packages/ui-stencil/src/components/internal/orama-input/orama-input.tsx b/packages/ui-stencil/src/components/internal/orama-input/orama-input.tsx index 935064f5..1c9d5096 100644 --- a/packages/ui-stencil/src/components/internal/orama-input/orama-input.tsx +++ b/packages/ui-stencil/src/components/internal/orama-input/orama-input.tsx @@ -5,9 +5,9 @@ import { AttributeUtils } from '../../../services/AttributeUtils'; type BaseInputProps = { name?: string; - placeholder?: string; size?: 'small' | 'medium' | 'large'; type?: 'text' | 'password' | 'email' | 'number' | 'tel' | 'url' | 'search'; + placeholder?: string; defaultValue?: string; }; @@ -34,8 +34,9 @@ export class Input { @Prop() size?: InputProps['size'] = 'medium'; @Prop() label?: InputProps['label']; @Prop() type?: InputProps['type'] = 'text'; + @Prop() placeholder?: InputProps['placeholder']; @Prop() labelForScreenReaders?: InputProps['labelForScreenReaders']; - @Prop({ mutable: true }) defaultValue: InputProps['defaultValue']; + @Prop() defaultValue: InputProps['defaultValue']; @Event({ eventName: 'oramaInputChanged', @@ -67,7 +68,7 @@ export class Input { const inputClass = `input input--${this.size}`; const labelClass = `label ${this.labelForScreenReaders ? 'sr-only' : ''}`; - const declaredProps = ['id', 'name', 'type', 'class', 'onInput', 'value', 'label-for-screen-readers', 'default-value']; + const declaredProps = ['id', 'name', 'type', 'class', 'onInput', 'value', 'label-for-screen-readers', 'default-value', 'placeholder']; const inputProps = AttributeUtils.getNonExplicitAttributes(this.el, declaredProps); const isSearch = this.type === 'search'; @@ -90,6 +91,7 @@ export class Input { type={this.type} value={this.value} onInput={event => this.handleChange(event)} + placeholder={this.placeholder} {...inputProps} /> {isSearch && !!this.value && ( diff --git a/packages/ui-stencil/src/components/internal/orama-input/readme.md b/packages/ui-stencil/src/components/internal/orama-input/readme.md index d439f3b9..72b76663 100644 --- a/packages/ui-stencil/src/components/internal/orama-input/readme.md +++ b/packages/ui-stencil/src/components/internal/orama-input/readme.md @@ -11,6 +11,7 @@ | `label` | `label` | | `string` | `undefined` | | `labelForScreenReaders` | `label-for-screen-readers` | | `string` | `undefined` | | `name` | `name` | | `string` | `undefined` | +| `placeholder` | `placeholder` | | `string` | `undefined` | | `size` | `size` | | `"large" \| "medium" \| "small"` | `'medium'` | | `type` | `type` | | `"email" \| "number" \| "password" \| "search" \| "tel" \| "text" \| "url"` | `'text'` | diff --git a/packages/ui-stencil/src/components/internal/orama-search-results/orama-search-results.scss b/packages/ui-stencil/src/components/internal/orama-search-results/orama-search-results.scss new file mode 100644 index 00000000..3f261f56 --- /dev/null +++ b/packages/ui-stencil/src/components/internal/orama-search-results/orama-search-results.scss @@ -0,0 +1,22 @@ +.list { + list-style: none; + padding: 0; + margin: 0; +} + +.list-item { + display: flex; + gap: var(--spacing-m, $spacing-m); + align-items: center; + padding: var(--spacing-m, $spacing-m); + border-radius: var(--radius-s, $radius-s); + background-color: var(--background-color-secondary, background-color('secondary')); + margin-top: var(--spacing-s, $spacing-s); +} + +.collapsed { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + direction: rtl; +} diff --git a/packages/ui-stencil/src/components/internal/orama-search-results/orama-search-results.tsx b/packages/ui-stencil/src/components/internal/orama-search-results/orama-search-results.tsx new file mode 100644 index 00000000..cb9b14f6 --- /dev/null +++ b/packages/ui-stencil/src/components/internal/orama-search-results/orama-search-results.tsx @@ -0,0 +1,36 @@ +import { Component, Host, h, Element, Prop } from '@stencil/core'; + +export type SearchResultsProps = { + items: { title: string; description: string , path?: string }[]; +}; + +@Component({ + tag: 'orama-search-results', + styleUrl: 'orama-search-results.scss', +}) +export class SearchResults { + @Element() el: HTMLUListElement; + + @Prop() items: SearchResultsProps['items'] = []; + + render() { + if (!this.items.length) { + return null; + } + + return ( + +
    + {this.items.map((item) => ( +
  • +
    + {item.title} + +
    +
  • + ))} +
+
+ ); + } +} diff --git a/packages/ui-stencil/src/components/internal/orama-search-results/readme.md b/packages/ui-stencil/src/components/internal/orama-search-results/readme.md new file mode 100644 index 00000000..8827169e --- /dev/null +++ b/packages/ui-stencil/src/components/internal/orama-search-results/readme.md @@ -0,0 +1,35 @@ +# orama-search-results + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| -------- | --------- | ----------- | ---------------------------------------------------------- | ------- | +| `items` | -- | | `{ title: string; description: string; path?: string; }[]` | `[]` | + + +## Dependencies + +### Used by + + - [orama-search](../orama-search) + +### Depends on + +- [orama-text](../orama-text) + +### Graph +```mermaid +graph TD; + orama-search-results --> orama-text + orama-search --> orama-search-results + style orama-search-results fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/ui-stencil/src/components/internal/orama-search/orama-search.tsx b/packages/ui-stencil/src/components/internal/orama-search/orama-search.tsx index 85ddb58b..5c41e923 100644 --- a/packages/ui-stencil/src/components/internal/orama-search/orama-search.tsx +++ b/packages/ui-stencil/src/components/internal/orama-search/orama-search.tsx @@ -1,17 +1,20 @@ import { OramaClient } from '@oramacloud/client' import { Component, Host, State, Watch, h } from '@stencil/core' import { SearchService } from '../../../services/SearchService' -import { searchContext } from '../../../context/searchContext' +import { searchState, searchStore } from '../../../context/searchContext' +import type { SearchResultsProps } from '../orama-search-results/orama-search-results' @Component({ tag: 'orama-search', styleUrl: 'orama-search.scss', shadow: true }) + export class OramaSearch { private searchService!: SearchService - @State() inputValue = '' + @State() searchValue = '' + @State() searchResults: SearchResultsProps['items'] = [] // TODO: We probably want to use this oramaClient both in chat and search. We may want to uplift orama client to be a singleton componentWillLoad() { @@ -24,42 +27,36 @@ export class OramaSearch { this.searchService = new SearchService(oramaClient) } - @Watch('inputValue') - handleInputValueChange(newValue: string) { + @Watch('searchValue') + handleSearchValueChange(newValue: string) { this.searchService.search(newValue) - console.log(searchContext.hits) + searchStore.onChange('hits', (hits) => { + this.searchResults = hits.map((hit) => ({ + title: hit.document.title, + description: hit.document.content, + path: hit.document.path + })) + }) + } + + + onSearch(e: Event) { + this.searchValue = (e.target as HTMLInputElement).value + searchState.term = this.searchValue } render() { return ( -
-

Search Placeholder

-
- {searchContext.hits.map((hit) => ( -
-
{hit.document.title}
-
{hit.document.content}
-
- ))} -
-
(this.inputValue = (e.target as HTMLInputElement).value)} + type='search' + onInput={this.onSearch.bind(this)} + labelForScreenReaders='Search...' + placeholder='Search...' /> +
) diff --git a/packages/ui-stencil/src/components/internal/orama-search/readme.md b/packages/ui-stencil/src/components/internal/orama-search/readme.md index e6af9894..4992e0e5 100644 --- a/packages/ui-stencil/src/components/internal/orama-search/readme.md +++ b/packages/ui-stencil/src/components/internal/orama-search/readme.md @@ -14,11 +14,14 @@ ### Depends on - [orama-input](../orama-input) +- [orama-search-results](../orama-search-results) ### Graph ```mermaid graph TD; orama-search --> orama-input + orama-search --> orama-search-results + orama-search-results --> orama-text search-box --> orama-search style orama-search fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/packages/ui-stencil/src/components/internal/orama-text/orama-text.tsx b/packages/ui-stencil/src/components/internal/orama-text/orama-text.tsx index cb21d3c2..5394e031 100644 --- a/packages/ui-stencil/src/components/internal/orama-text/orama-text.tsx +++ b/packages/ui-stencil/src/components/internal/orama-text/orama-text.tsx @@ -1,4 +1,5 @@ -import { Component, Prop, h, State } from '@stencil/core'; +import { Component, Prop, h, State, Element } from '@stencil/core'; +// import { AttributeUtils } from '../../../services/AttributeUtils'; export interface TextProps { /** it defines the HTML tag to be used */ @@ -18,6 +19,8 @@ export interface TextProps { * */ export class OramaText implements TextProps { + @Element() el: HTMLElement + @Prop() as?: TextProps['as'] = 'p' @Prop() styledAs?: TextProps['styledAs'] @Prop() class?: string @@ -26,12 +29,16 @@ export class OramaText implements TextProps { render() { const Tag = this.as; + // const declaredProps = ['as', 'styled-as', 'class'] + // const textProps = AttributeUtils.getNonExplicitAttributes(this.el, declaredProps) return ( - + ); diff --git a/packages/ui-stencil/src/components/internal/orama-text/readme.md b/packages/ui-stencil/src/components/internal/orama-text/readme.md index 9b7bb8e8..efe38521 100644 --- a/packages/ui-stencil/src/components/internal/orama-text/readme.md +++ b/packages/ui-stencil/src/components/internal/orama-text/readme.md @@ -18,12 +18,14 @@ - [orama-chat](../orama-chat) - [orama-chat-assistent-message](../orama-chat-messages-container/orama-chat-assistent-message) + - [orama-search-results](../orama-search-results) ### Graph ```mermaid graph TD; orama-chat --> orama-text orama-chat-assistent-message --> orama-text + orama-search-results --> orama-text style orama-text fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/packages/ui-stencil/src/components/search-box-toggler/search-box-toggler.tsx b/packages/ui-stencil/src/components/search-box-toggler/search-box-toggler.tsx index dc6eb8b5..0915cdaa 100644 --- a/packages/ui-stencil/src/components/search-box-toggler/search-box-toggler.tsx +++ b/packages/ui-stencil/src/components/search-box-toggler/search-box-toggler.tsx @@ -1,5 +1,5 @@ import { Component, Host, h } from '@stencil/core' -import { searchContext } from '../../context/searchContext' +import { searchState } from '../../context/searchContext' @Component({ tag: 'search-box-toggler', @@ -13,7 +13,7 @@ export class SearchBoxToggler {