Skip to content

Commit

Permalink
feat(select): add search feature for select
Browse files Browse the repository at this point in the history
  • Loading branch information
Erbil Nas committed Dec 25, 2023
1 parent ecc8a30 commit 34302cd
Show file tree
Hide file tree
Showing 5 changed files with 392 additions and 10 deletions.
81 changes: 81 additions & 0 deletions src/components/select/bl-select.css
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,15 @@
left: var(--left);
}

.popover-no-result {
display: flex;
flex-direction: column;
gap: var(--bl-size-2xs);
align-items: center;
justify-content: center;
height: var(--menu-height);
}

.select-open .popover {
display: flex;
border: solid 1px var(--border-focus-color);
Expand Down Expand Up @@ -361,3 +370,75 @@ legend span {
bottom: 0;
border-bottom: 1px solid var(--bl-color-neutral-lighter);
}

.search-bar-input {
font: var(--bl-font-title-3-regular);
font-size: var(--font-size);
color: var(--text-color);
border: none;
outline: none;
background-color: transparent;
width: 100%;
padding: 0;
margin: 0;
box-sizing: border-box;
height: var(--height);
}

.search-bar-input::placeholder {
color: var(--placeholder-color);
}

.search-bar-input:focus-visible {
outline: none;
}

.search-mag-icon {
animation: wiggle 2s linear infinite;
}

.search-loading-icon {
animation: spin 1s linear infinite;
}

@keyframes spin {
from {
transform: rotate(0deg);
}

to {
transform: rotate(360deg);
}
}

@keyframes wiggle {
0%,
7% {
transform: rotateZ(0);
}

15% {
transform: rotateZ(-15deg);
}

20% {
transform: rotateZ(10deg);
}

25% {
transform: rotateZ(-10deg);
}

30% {
transform: rotateZ(6deg);
}

35% {
transform: rotateZ(-4deg);
}

40%,
100% {
transform: rotateZ(0);
}
}
24 changes: 24 additions & 0 deletions src/components/select/bl-select.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ export const SelectTemplate = (args) => html`<bl-select
?success=${args.success}
?view-select-all=${args.viewSelectAll}
select-all-text=${ifDefined(args.selectAllText)}
?search-bar=${args.searchBar}
?search-bar-loading-state=${args.searchBarLoadingState}
search-bar-placeholder=${ifDefined(args.searchBarPlaceholder)}
size=${ifDefined(args.size)}
help-text=${ifDefined(args.helpText)}
invalid-text=${ifDefined(args.customInvalidText)}
Expand Down Expand Up @@ -249,6 +252,27 @@ Select component can be disabled by using `disabled` attribute.
</Story>
</Canvas>

## Searchable

<bl-badge icon="document">[ADR](https://github.com/Trendyol/baklava/issues/265#issuecomment-1845414216)</bl-badge>
<bl-badge icon="puzzle">[Figma](https://www.figma.com/file/RrcLH0mWpIUy4vwuTlDeKN/Baklava-Design-Guide?node-id=13059%3A6927)</bl-badge>

Select component can be searchable by using `search-bar` attribute.

<Canvas>
<Story name="Searchable" args={{ searchBar: true, searchBarPlaceholder: 'Search your options' }}>
{SelectTemplate.bind({})}
</Story>

<Story name="Searchable with Multiple Option" args={{ searchBar: true, searchBarPlaceholder: 'Search your options', multiple: true }}>
{SelectTemplate.bind({})}
</Story>

<Story name="Searchable with Loading State" args={{ searchBar: true, searchBarPlaceholder: 'Search your options', multiple: true, searchBarLoadingState: true }}>
{SelectTemplate.bind({})}
</Story>
</Canvas>

## `bl-select` Event

Select component fires `bl-select` event once selection changes. This event has a payload in the type of
Expand Down
126 changes: 125 additions & 1 deletion src/components/select/bl-select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,106 @@ describe("bl-select", () => {
expect(selectOption).is.not.exist;
});

it("should show search input if search-bar attribute is given", async () => {
const el = await fixture<BlSelect>(html`<bl-select search-bar>
<bl-select-option value="tr">Turkey</bl-select-option>
<bl-select-option value="en">United States of America</bl-select-option>
</bl-select>`);

const searchInput = el.shadowRoot?.querySelector<HTMLInputElement>("fieldset input");

expect(searchInput).to.exist;
});

it("should search 'Turkey' when 'turkey' is typed", async () => {
const el = await fixture<BlSelect>(html`<bl-select search-bar>
<bl-select-option value="tr">Turkey</bl-select-option>
<bl-select-option value="en">United States of America</bl-select-option>
</bl-select>`);

const searchInput = el.shadowRoot?.querySelector<HTMLInputElement>("fieldset input");

searchInput?.focus();

await sendKeys({
type: "turkey",
});

el.options.forEach(option => {
if (option.innerText === "Turkey") {
expect(option.hidden).to.be.false;
} else {
expect(option.hidden).to.be.true;
}
});
});

it("should show loading icon when the search loading state is true", async () => {
const el = await fixture<BlSelect>(html`<bl-select search-bar>
<bl-select-option value="tr">Turkey</bl-select-option>
<bl-select-option value="en">United States of America</bl-select-option>
</bl-select>`);

const searchInput = el.shadowRoot?.querySelector<HTMLInputElement>("fieldset input");

searchInput?.focus();

const loadingIcon = el.shadowRoot?.querySelector<HTMLInputElement>("fieldset bl-icon");

await sendKeys({
type: "turkey",
});

expect(loadingIcon).to.exist;
});

it("should be displayed a 'no result' message if the searched term does not match with any option", async () => {
const el = await fixture<BlSelect>(html`<bl-select search-bar>
<bl-select-option value="tr">Turkey</bl-select-option>
<bl-select-option value="en">United States of America</bl-select-option>
</bl-select>`);

const searchInput = el.shadowRoot?.querySelector<HTMLInputElement>("fieldset input");

searchInput?.focus();

await sendKeys({
type: "netherlands",
});

const noResultContainer = el.shadowRoot?.querySelector<HTMLInputElement>(".popover .popover-no-result");
const noResultMessage = el.shadowRoot?.querySelector<HTMLInputElement>(".popover .popover-no-result span")?.innerText;


el.options.forEach(option => {
expect(option.hidden).to.be.true;
});

expect(noResultContainer).to.exist;
expect(noResultMessage).to.equal("No Data Found");
});

it("should be cleared the search input if the user click on the clear search button", async () => {
const el = await fixture<BlSelect>(html`<bl-select search-bar>
<bl-select-option value="tr">Turkey</bl-select-option>
<bl-select-option value="en">United States of America</bl-select-option>
</bl-select>`);

const searchInput = el.shadowRoot?.querySelector<HTMLInputElement>("fieldset input");

searchInput?.focus();

await sendKeys({
type: "netherlands",
});

const clearSearchButton = el.shadowRoot?.querySelector<BlButton>(".popover .popover-no-result bl-button");

clearSearchButton?.click();

setTimeout(() => expect(searchInput?.value).to.equal(""));
});

describe("additional selection counter", () => {
let el: BlSelect;

Expand Down Expand Up @@ -649,5 +749,29 @@ describe("bl-select", () => {
expect(selectAll.indeterminate).to.be.false;
expect(selectAll.checked).to.be.false;
});
});
});

describe("events", () => {
it("should fire search event when 'turkey' is typed", async () => {
const el = await fixture<BlSelect>(html`<bl-select search-bar>
<bl-select-option value="tr">Turkey</bl-select-option>
<bl-select-option value="en">United States of America</bl-select-option>
</bl-select>`);

const searchInput = el.shadowRoot?.querySelector<HTMLInputElement>("fieldset input");

if (searchInput) {
searchInput.focus();

searchInput.value = "turkey";
}

setTimeout(() => searchInput?.dispatchEvent(new Event("input")));

const event = await oneEvent(el, "bl-search");

expect(event).to.exist;
expect(event.detail).to.equal("turkey");
});
});
});
Loading

0 comments on commit 34302cd

Please sign in to comment.