mirror of
https://github.com/danbulant/Portfolio
synced 2026-07-05 11:00:56 +00:00
feat: init school select component
This commit is contained in:
parent
8fbefbf9ce
commit
60323b9be5
4 changed files with 177 additions and 36 deletions
|
|
@ -1,36 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import LL from '$i18n/i18n-svelte';
|
|
||||||
|
|
||||||
import type { School } from '$lib/stores/candidate';
|
|
||||||
// TODO
|
|
||||||
// import AutoComplete from 'simple-svelte-autocomplete';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
// import schoollistString from '$lib/assets/schoollist.txt';
|
|
||||||
|
|
||||||
let schools: string[] = [];
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
schools = await fetch('/schoollist.txt')
|
|
||||||
.then((response) => response.text())
|
|
||||||
.then((text) => text.split(';'));
|
|
||||||
});
|
|
||||||
|
|
||||||
export let selectedSchool: School;
|
|
||||||
export let schoolName: string = selectedSchool.name;
|
|
||||||
$: selectedSchool.name = schoolName;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<div>
|
|
||||||
<span>
|
|
||||||
{$LL.input.selectedSchool()}: {selectedSchool.name}
|
|
||||||
</span>
|
|
||||||
<!-- TODO -->
|
|
||||||
<!-- <AutoComplete items={schools} bind:selectedItem={schoolName} /> -->
|
|
||||||
<input type="text" bind:value={schoolName} />
|
|
||||||
</div>
|
|
||||||
<div class="flex">
|
|
||||||
<span>{$LL.input.fieldOfStudy()}: </span>
|
|
||||||
<input type="text" bind:value={selectedSchool.field} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
<script lang="ts">
|
||||||
|
export let itemLabel: string;
|
||||||
|
export let highlighted: boolean;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<li class="autocomplete-items" class:autocomplete-active={highlighted} on:click on:keydown={null}>
|
||||||
|
{@html itemLabel}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
li.autocomplete-items {
|
||||||
|
list-style: none;
|
||||||
|
border-bottom: 1px solid #d4d4d4;
|
||||||
|
z-index: 99;
|
||||||
|
/*position the autocomplete items to be the same width as the container:*/
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.autocomplete-items:hover {
|
||||||
|
/*when hovering an item:*/
|
||||||
|
background-color: #81921f;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.autocomplete-items:active {
|
||||||
|
/*when navigating through the items using the arrow keys:*/
|
||||||
|
background-color: DodgerBlue !important;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-active {
|
||||||
|
/*when navigating through the items using the arrow keys:*/
|
||||||
|
background-color: DodgerBlue !important;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import schoollistString from '$lib/assets/schoollist.txt?raw';
|
||||||
|
import School from './School.svelte';
|
||||||
|
import type { School as SchoolType } from '$lib/stores/candidate';
|
||||||
|
|
||||||
|
const schoolList: Array<string> = schoollistString.split(';');
|
||||||
|
|
||||||
|
let filteredSchools: Array<string> = [];
|
||||||
|
|
||||||
|
const filterSchools = () => {
|
||||||
|
let storageArr: Array<string> = [];
|
||||||
|
if (schoolNameInputValue) {
|
||||||
|
schoolList.forEach((school) => {
|
||||||
|
if (school.toLowerCase().startsWith(schoolNameInputValue.toLowerCase())) {
|
||||||
|
storageArr = [...storageArr, makeMatchBold(school)];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
filteredSchools = storageArr;
|
||||||
|
};
|
||||||
|
|
||||||
|
let searchInput: HTMLInputElement;
|
||||||
|
let optionsList: HTMLUListElement;
|
||||||
|
|
||||||
|
let schoolNameInputValue = '';
|
||||||
|
let schoolFieldInputValue = '';
|
||||||
|
|
||||||
|
$: if (!schoolNameInputValue) {
|
||||||
|
filteredSchools = [];
|
||||||
|
hiLiteIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const setInputVal = (schoolName: string) => {
|
||||||
|
schoolNameInputValue = removeBold(schoolName);
|
||||||
|
filteredSchools = [];
|
||||||
|
hiLiteIndex = -1;
|
||||||
|
searchInput.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeMatchBold = (str: string) => {
|
||||||
|
let matched = str.substring(0, schoolNameInputValue.length);
|
||||||
|
let makeBold = `<strong>${matched}</strong>`;
|
||||||
|
let boldedMatch = str.replace(matched, makeBold);
|
||||||
|
return boldedMatch;
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeBold = (str: string) => {
|
||||||
|
return str.replace(/<(.)*?>/g, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
let hiLiteIndex: number = 0;
|
||||||
|
|
||||||
|
const navigateList = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'ArrowDown') {
|
||||||
|
if (hiLiteIndex < filteredSchools.length - 1) {
|
||||||
|
hiLiteIndex++;
|
||||||
|
// scroll optionsList
|
||||||
|
let option = optionsList.children[hiLiteIndex];
|
||||||
|
if (option) {
|
||||||
|
option.scrollIntoView({ block: 'nearest' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (e.key === 'ArrowUp') {
|
||||||
|
if (hiLiteIndex > 0) {
|
||||||
|
hiLiteIndex--;
|
||||||
|
}
|
||||||
|
} else if (e.key === 'Enter') {
|
||||||
|
if (hiLiteIndex > -1) {
|
||||||
|
setInputVal(filteredSchools[hiLiteIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export let selectedSchool: SchoolType;
|
||||||
|
|
||||||
|
$: selectedSchool.name = schoolNameInputValue;
|
||||||
|
$: selectedSchool.field = schoolFieldInputValue;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={navigateList} />
|
||||||
|
|
||||||
|
<div class="autocomplete">
|
||||||
|
<div class="flex">
|
||||||
|
<input
|
||||||
|
class="flex-1"
|
||||||
|
type="text"
|
||||||
|
bind:this={searchInput}
|
||||||
|
bind:value={schoolNameInputValue}
|
||||||
|
on:input={filterSchools}
|
||||||
|
/>
|
||||||
|
<input class="ml-2 w-2/5" type="text" bind:value={schoolFieldInputValue} />
|
||||||
|
</div>
|
||||||
|
{#if filteredSchools.length > 0}
|
||||||
|
<ul bind:this={optionsList} class="schoolAutocompleteList">
|
||||||
|
{#each filteredSchools as country, i}
|
||||||
|
<School
|
||||||
|
itemLabel={country}
|
||||||
|
highlighted={i === hiLiteIndex}
|
||||||
|
on:click={() => setInputVal(country)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
div,
|
||||||
|
input {
|
||||||
|
@apply w-full;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
@apply relative flex items-center justify-center;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
@apply hover:border-sspsBlue w-full rounded-lg border border-2 bg-[#f8fafb] p-3 text-xl shadow-lg outline-none transition-colors duration-300;
|
||||||
|
}
|
||||||
|
div span {
|
||||||
|
@apply absolute right-0 top-0 bottom-0 my-auto flex bg-transparent p-3;
|
||||||
|
}
|
||||||
|
.withIcon {
|
||||||
|
@apply pr-14;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
@apply border-red-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete {
|
||||||
|
@apply relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schoolAutocompleteList {
|
||||||
|
@apply absolute top-20 z-50;
|
||||||
|
@apply w-full;
|
||||||
|
@apply max-h-72 overflow-scroll;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in a new issue