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

Autocomplete component #988

Closed
wants to merge 7 commits 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
1 change: 1 addition & 0 deletions src/docs/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export const menuNavLinks: any = [
{ href: '/components/app-bar', label: 'App Bar', keywords: 'header, top, bar, title' },
{ href: '/components/app-rail', label: 'App Rail', keywords: 'nav, navigation, tile, sidebar' },
{ href: '/components/app-shell', label: 'App Shell', keywords: 'layout, header, footer, sidebar, page, content' },
{ href: '/components/auto-complete', label: 'Autocomplete', keywords: 'input, filter, fuzzy, auto-complete' },
{ href: '/components/avatars', label: 'Avatars', keywords: 'image, initial, filter' },
{ href: '/components/conic-gradients', label: 'Conic Gradients', keywords: 'chart, graph, circle, pie, spinner, legend' },
{ href: '/components/file-buttons', label: 'File Buttons', keywords: 'upload, form, input, file, media' },
Expand Down
86 changes: 86 additions & 0 deletions src/lib/components/AutoComplete/AutoComplete.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<script lang="ts">
import type { CssClasses } from '$lib';
import type { Mode } from "$lib/components/AutoComplete/types";
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();

/** Define variable for live filtering */
export let searchTerm: string;
/** Define values for the list */
export let values: { label: string; value: string }[];
/**
* Provide a whitelist of accepted values.
* Defaults to all values provided by the user.
* @type {{ label: string, value: string }[]}
*/
export let whitelist: { label: string, value: string }[] = [...values];
/** Define mode for determining how filtering works: fuzzy, exclude (takes search string of CVS and excludes them from the list)*/
export let mode:Mode = "fuzzy";

// Props (styles)
/** Provide classes to set padding styles. */
export let padding: CssClasses = 'p-2';
/** Provide classes to set border radius styles. */
export let rounded: CssClasses = 'rounded-container-token';
/** Provide classes to set max height styles. */
export let maxHeight: CssClasses = 'max-h-[100px]';

// Props (items)
/** Provide classes or a variant to style the hover. */
export let itemHover: CssClasses = 'variant-soft-primary';
/** Provide classes to set values background styles. */
export let background: CssClasses = 'variant-ringed-primary';
/** Provide classes to set values for overflow styles. */
export let overflowY: CssClasses = 'overflow-y-auto';

// Classes
const cBase = 'cursor-pointer';


function onSelectHandler(selected: { label: string; value: string }): void {
/** @event {{ event: ClickEvent }} click - When an item is clicked. */
dispatch('select', { selected });
}

/** Fuzzy filters values based on the search term. Case insensitive. */
$: filteredValues = () => {
if (!searchTerm) return [...values];

//TODO: Want to include the ability to include "separators" ex: user enters - foo, bar, foobar:
// list only has fizz, buzz, and fizzbuzz left for the user to pick
//TODO: Use regex match instead of includes, this should work for the separators as well because it returns and array and I can ignore the separator value in the match
if(mode === "exclude"){
return values.filter((row) => {
const rowFormatted = JSON.stringify(row).toLowerCase();
const whiteListFormatted = JSON.stringify(whitelist).toLowerCase();

if(!rowFormatted.includes(searchTerm.toLowerCase()) && whiteListFormatted.includes(searchTerm.toLowerCase())) return row;
});
}
else{
return values.filter((row) => {
const rowFormatted = JSON.stringify(row).toLowerCase();
const whiteListFormatted = JSON.stringify(whitelist).toLowerCase();

if(rowFormatted.includes(searchTerm.toLowerCase()) && whiteListFormatted.includes(searchTerm.toLowerCase())) return row;
});
}
};

// Reactive
$: classesBase = `${cBase} ${padding} ${rounded} ${maxHeight} ${overflowY} ${background} ${$$props.class ?? ''}`;
</script>

<div class="autocomplete list {classesBase}" data-testid="auto-complete">
{#each filteredValues() as value}
<div
class="autocomplete-item flex-auto hover:{itemHover}"
on:click={() => {
onSelectHandler(value);
}}
on:keypress
>
{value.label}
</div>
{/each}
</div>
2 changes: 2 additions & 0 deletions src/lib/components/AutoComplete/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** Provides intellisense to compontent for available autocomplete functionality. */
export type Mode = "exclude" | "fuzzy"
30 changes: 30 additions & 0 deletions src/routes/(inner)/components/auto-complete/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script lang="ts">
import AutoComplete from '$lib/components/AutoComplete/AutoComplete.svelte';

type ValidValues = { label: string; value: string }[];

// Local
let inputValue = '';
let anythingList: ValidValues = [
{ label: 'Foo', value: 'Foo' },
{ label: 'Bar', value: 'Bar' },
{ label: 'FooBar', value: 'FooBar' },
{ label: 'Fizz', value: 'Fizz' },
{ label: 'Buzz', value: 'Buzz' },
{ label: 'FizzBuzz', value: 'FizzBuzz' }
];

let whitelist = [{label: "Foo", value: "Foo"}, {label: "Bar", value: "Bar"}]

function onValueSelect(event: CustomEvent): void {
inputValue = event.detail.selected.label;
}
</script>

<div class="page-container">
<label class="label" for="autocomplete-search">
<h3>Via Input</h3>
<input class="input" type="search" name="autocomplete-search" bind:value={inputValue} placeholder="Begin typing to filter..." />
<AutoComplete bind:searchTerm={inputValue} values={anythingList} mode="exclude" on:select={onValueSelect} />
</label>
</div>