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: Add search bar component #290

Merged
merged 40 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
27c419e
feat: Create autocomplete component
Trishu-Patel Nov 1, 2024
d65c0d5
feat: Add search and loading icons
Trishu-Patel Nov 4, 2024
f728029
chore: Copy functionality from input field
Trishu-Patel Nov 4, 2024
230f852
feat: Add search icon and loading indicator
Trishu-Patel Nov 8, 2024
ad3d953
feat: Refactor dropdown styling
Trishu-Patel Nov 8, 2024
9c32cd5
refactor: Use onClickOutside modifier
Trishu-Patel Nov 8, 2024
d7693b0
feat: Separate search string and value
Trishu-Patel Nov 12, 2024
7cec562
feat: Allow arrow keys to select items
Trishu-Patel Nov 12, 2024
971d1eb
feat: Add restartable task
Trishu-Patel Nov 14, 2024
7112bcf
feat: Filter items on search
Trishu-Patel Nov 14, 2024
9b5b6fb
refactor: Simplify focusing and search logic
Trishu-Patel Nov 15, 2024
0b58e56
feat: Pass query to component
Trishu-Patel Nov 15, 2024
1687c81
feat: Implement old nrg api
Trishu-Patel Nov 15, 2024
8f1ac10
feat: Blur text input on selection
Trishu-Patel Nov 15, 2024
d1a1b4d
feat: Make search options scrollable
Trishu-Patel Nov 18, 2024
bc3cee3
feat: Add a clear button
Trishu-Patel Nov 19, 2024
56720f3
chore: Alphabetize api
Trishu-Patel Nov 19, 2024
c4608a3
chore: Rename autocomplete into search
Trishu-Patel Nov 19, 2024
1d1161e
chore: Rename variables and functions
Trishu-Patel Nov 19, 2024
c096c85
test: Add simple search tests
Trishu-Patel Nov 19, 2024
8e773b4
feat: Add search to field component
Trishu-Patel Nov 20, 2024
d415104
feat: Enable objects to be passed in search component
Trishu-Patel Nov 22, 2024
5910f6d
feat: Return object on select
Trishu-Patel Nov 22, 2024
9ee748e
feat: Add on change when selecting an item
Trishu-Patel Nov 25, 2024
2fe1d81
feat: Add display path
Trishu-Patel Nov 26, 2024
07454c4
feat: Update search string when value is changed
Trishu-Patel Nov 26, 2024
83667f7
fix: Show invalid input message
Trishu-Patel Nov 27, 2024
3f1e5e8
docs: Add string and object options to docs
Trishu-Patel Dec 2, 2024
9d4f53c
fix: Correctly display labels if value changes
Trishu-Patel Dec 2, 2024
57ab5db
chore: Update variable and function names
Trishu-Patel Dec 2, 2024
1ec3699
fix: Do not blur on search
Trishu-Patel Dec 3, 2024
f7a99cc
fix: Display correct value if serialization path in null
Trishu-Patel Dec 3, 2024
fe5dcd3
chore: Update onInsert import path
Trishu-Patel Dec 3, 2024
46b5cb0
chore: Linting and formatting
Trishu-Patel Dec 3, 2024
16033ff
feat: Peer review
Trishu-Patel Dec 17, 2024
9f432ad
refactor: Use nrg text input
Trishu-Patel Dec 19, 2024
5d8ca5f
docs: Formatting
Trishu-Patel Dec 19, 2024
308db24
chore: Apply placeholder with spread attributes
Trishu-Patel Dec 19, 2024
6e61785
chore: Update tests
Trishu-Patel Dec 19, 2024
59960fa
docs: Update docs and formatting
Trishu-Patel Dec 19, 2024
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
334 changes: 334 additions & 0 deletions apps/docs-app/app/components/f/form/search.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
import { fn } from '@ember/helper';
import { action, set } from '@ember/object';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { Search, bind } from '@nrg-ui/core';
import { timeout } from 'ember-concurrency';
import FreestyleUsage from 'ember-freestyle/components/freestyle/usage';
import FreestyleSection from 'ember-freestyle/components/freestyle-section';

import CodeBlock from '../../code-block';

// TypeScript doesn't recognize that this function is used in the template
// eslint-disable-next-line @typescript-eslint/no-unused-vars
TSenter marked this conversation as resolved.
Show resolved Hide resolved
function log(...msg: string[]) {
console.log(msg.join(' '));
}

class Model {
@tracked
property = '';
}

export default class extends Component {
model = new Model();

@tracked
class = '';

@tracked
basic = false;

@tracked
clearable = false;

@tracked
disabled = false;

@tracked
hideSearchIcon = false;

@tracked
stringOptions = [
'Apple',
'Pear',
'Orange',
'Banana',
'Grape',
'Strawberry',
'Mango',
'Pineapple',
'Peach',
'Cherry',
'Blueberry',
'Watermelon',
'Papaya',
'Kiwi',
'Plum',
'Apricot',
'Pomegranate',
'Lemon',
'Lime',
'Raspberry',
'Blackberry',
'Coconut',
'Dragon fruit',
'Lychee',
'Fig',
'Tangerine',
];

@tracked
objectOptions = [
{ key: 'Option 1', fruit: 'Apple' },
{ key: 'Option 2', fruit: 'Pear' },
{ key: 'Option 3', fruit: 'Orange' },
{ key: 'Option 4', fruit: 'Banana' },
{ key: 'Option 5', fruit: 'Grape' },
{ key: 'Option 6', fruit: 'Strawberry' },
{ key: 'Option 7', fruit: 'Mango' },
{ key: 'Option 8', fruit: 'Pineapple' },
{ key: 'Option 9', fruit: 'Peach' },
{ key: 'Option 10', fruit: 'Cherry' },
{ key: 'Option 11', fruit: 'Blueberry' },
{ key: 'Option 12', fruit: 'Watermelon' },
{ key: 'Option 13', fruit: 'Papaya' },
{ key: 'Option 14', fruit: 'Kiwi' },
{ key: 'Option 15', fruit: 'Plum' },
{ key: 'Option 16', fruit: 'Apricot' },
{ key: 'Option 17', fruit: 'Pomegranate' },
{ key: 'Option 18', fruit: 'Lemon' },
{ key: 'Option 19', fruit: 'Lime' },
{ key: 'Option 20', fruit: 'Raspberry' },
{ key: 'Option 21', fruit: 'Blackberry' },
{ key: 'Option 22', fruit: 'Coconut' },
{ key: 'Option 23', fruit: 'Dragon fruit' },
{ key: 'Option 24', fruit: 'Lychee' },
{ key: 'Option 25', fruit: 'Fig' },
{ key: 'Option 26', fruit: 'Tangerine' },
];

@tracked
loading = false;

@tracked
minCharacters = 1;

@tracked
noResultsLabel;

@tracked
placeholder;

@tracked
readonly = false;

@tracked
scrollable = true;

@tracked
searchTimeout = 300;

@tracked
value = '';

@action
async stringQuery(searchString: string) {
await timeout(1000);
return this.stringOptions.filter((item) =>
item.toLowerCase().includes(searchString.toLowerCase()),
);
}

@action
async objectQuery(searchString: string) {
await timeout(1000);

const things = this.objectOptions.filter((item) =>
item.fruit.toLowerCase().includes(searchString.toLowerCase()),
);
return things;
}

@action
update(key: string, value: unknown) {
set(this, key, value);
}

get stringOptionsSource() {
return JSON.stringify(this.stringOptions, null, 2);
}

get objectOptionsSource() {
return JSON.stringify(this.objectOptions, null, 2);
}

<template>
<FreestyleSection @name="Search" as |Section|>
<Section.subsection @name="Basic">
<FreestyleUsage>
<:example>
<Search
class={{this.class}}
@basic={{this.basic}}
@binding={{bind this.model "property"}}
@clearable={{this.clearable}}
@disabled={{this.disabled}}
@hideSearchIcon={{this.hideSearchIcon}}
@loading={{this.loading}}
@minCharacters={{this.minCharacters}}
@noResultsLabel={{this.noResultsLabel}}
@placeholder={{this.placeholder}}
@readonly={{this.readonly}}
@scrollable={{this.scrollable}}
@searchTimeout={{this.searchTimeout}}
@onChange={{fn log "The value changed to"}}
@onQuery={{this.stringQuery}}
/>
</:example>
<:api as |Args|>
<Args.String
@name="class"
@description="The class to apply to the input. Note that this is not an argument but rather a class applied directly to the input"
@value={{this.class}}
@onInput={{fn this.update "class"}}
@options={{this.classOptions}}
/>
<Args.Bool
@name="basic"
@defaultValue={{false}}
@description="When true, the border will be removed"
@value={{this.basic}}
@onInput={{fn this.update "basic"}}
/>
<Args.String
@name="binding"
@description="Create a two-way binding with the value"
@value={{this.model.property}}
@onInput={{fn this.update "model.property"}}
/>
<Args.Bool
@name="clearable"
@defaultValue={{false}}
@description="When true, adds a clear button"
@value={{this.clearable}}
@onInput={{fn this.update "clearable"}}
/>
<Args.Bool
@name="disabled"
@defaultValue={{false}}
@description="When true, the input will be disabled"
@value={{this.disabled}}
@onInput={{fn this.update "disabled"}}
/>
<Args.Bool
@name="hideSearchIcon"
@defaultValue={{false}}
@description="When true, the search icon will be hidden"
@value={{this.hideSearchIcon}}
@onInput={{fn this.update "hideSearchIcon"}}
/>
TSenter marked this conversation as resolved.
Show resolved Hide resolved
<Args.Bool
@name="loading"
@defaultValue={{false}}
@description="When true, the icon will be replaced with a loading spinner. Note: the loading indicator will not be displayed if the basic option is set to true"
@value={{this.loading}}
@onInput={{fn this.update "loading"}}
/>
<Args.Number
@name="minCharacters"
@defaultValue={{1}}
@description="The minimum number of characters needed to start a search"
@value={{this.minCharacters}}
@onInput={{fn this.update "minCharacters"}}
/>
<Args.String
@name="noResultsLabel"
@defaultValue="No results found"
@description="The label to display when no results are found"
@value={{this.noResultsLabel}}
@onInput={{fn this.update "noResultsLabel"}}
/>
<Args.String
@name="placeholder"
@defaultValue="Search"
@description="The placeholder text"
@value={{this.placeholder}}
@onInput={{fn this.update "placeholder"}}
/>
<Args.Bool
@name="readonly"
@defaultValue={{false}}
@description="When true, the input will be readonly"
@value={{this.readonly}}
@onInput={{fn this.update "readonly"}}
/>
<Args.Bool
@name="scrollable"
@defaultValue={{true}}
@description="Unless false, the dropdown will be scrollable"
@value={{this.scrollable}}
@onInput={{fn this.update "scrollable"}}
/>
<Args.Number
@name="searchTimeout"
@defaultValue={{300}}
@description="The amount of time to wait before searching"
@value={{this.searchTimeout}}
@onInput={{fn this.update "searchTimeout"}}
/>
<Args.Action
@name="onQuery"
@description="A function that return an array of string or object options"
>
<CodeBlock
@lang="typescript"
@code="(string: searchString) => T[]"
/>
</Args.Action>
</:api>
</FreestyleUsage>
</Section.subsection>

<Section.subsection @name="Object Options">
<FreestyleUsage>
<:example>
<Search
class={{this.class}}
@basic={{this.basic}}
@binding={{bind this.model "property"}}
@clearable={{this.clearable}}
@disabled={{this.disabled}}
@displayPath="fruit"
@hideSearchIcon={{this.hideSearchIcon}}
@loading={{this.loading}}
@minCharacters={{this.minCharacters}}
@noResultsLabel={{this.noResultsLabel}}
@placeholder={{this.placeholder}}
@readonly={{this.readonly}}
@scrollable={{this.scrollable}}
@searchTimeout={{this.searchTimeout}}
@serializationPath="key"
@onChange={{fn log "The value changed to"}}
@onQuery={{this.objectQuery}}
/>
</:example>
</FreestyleUsage>
</Section.subsection>
</FreestyleSection>
<div class="grid">
<div class="g-col-4">
<h3>String Options</h3>
<CodeBlock
class="border rounded p-3"
@lang="json"
@code={{this.stringOptionsSource}}
/>
</div>
<div class="g-col-4">
<h3>Object Options</h3>
<CodeBlock
class="border rounded p-3 scrollable"
@lang="json"
@code={{this.objectOptionsSource}}
/>
</div>
<div class="g-col-4">
<h3>Selected</h3>
<div class="border rounded p-3">
{{this.model.property}}
</div>
</div>
</div>
</template>
}
1 change: 1 addition & 0 deletions apps/docs-app/app/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Router.map(function () {
this.route('datetime');
this.route('phone-input');
this.route('radio-group');
this.route('search');
this.route('select');
this.route('text-area');
this.route('text-input');
Expand Down
1 change: 1 addition & 0 deletions apps/docs-app/app/templates/components.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<Item @name="Checkbox Group" @route="components.form.checkbox-group" />
<Item @name="Phone Input" @route="components.form.phone-input" />
<Item @name="Radio Group" @route="components.form.radio-group" />
<Item @name="Search" @route="components.form.search" />
<Item @name="Select" @route="components.form.select" />
<Item @name="Text Area" @route="components.form.text-area" />
<Item @name="Text Input" @route="components.form.text-input" />
Expand Down
5 changes: 5 additions & 0 deletions apps/docs-app/app/templates/components/form/search.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{page-title "Search"}}

<div class="container mx-auto">
<F::Form::Search />
</div>
Loading
Loading