mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
210 lines
6.5 KiB
Svelte
210 lines
6.5 KiB
Svelte
<script lang="ts">
|
|
import { DropList } from '$lib/components';
|
|
import { clickOnEnter } from '$lib/helpers/a11y';
|
|
import { createEventDispatcher, onMount } from 'svelte';
|
|
import { Label } from '.';
|
|
|
|
/* eslint no-undef: "off" */
|
|
type Option = $$Generic<{
|
|
value: string | boolean | number;
|
|
label: string;
|
|
data?: string[];
|
|
}>;
|
|
type OptionArray = Option[];
|
|
|
|
export let options: OptionArray;
|
|
export let id: string;
|
|
export let label: string;
|
|
export let name = 'elements';
|
|
export let optionalText: string | undefined = undefined;
|
|
export let tooltip: string | undefined = undefined;
|
|
export let showLabel = true;
|
|
export let placeholder = '';
|
|
export let required = false;
|
|
export let hideRequired = false;
|
|
export let disabled = false;
|
|
export let fullWidth = false;
|
|
export let autofocus = false;
|
|
export let interactiveOutput = false;
|
|
// stretch is used inside of a flex container to give the element flex:1
|
|
export let stretch = true;
|
|
export let search = '';
|
|
// The actual selected value
|
|
export let value: Option['value'];
|
|
$: selectedOption = options.find((option) => option.value === value);
|
|
|
|
let element: HTMLInputElement;
|
|
let hasFocus = false;
|
|
let selected: number = null;
|
|
|
|
$: if (!hasFocus) {
|
|
selected = null;
|
|
}
|
|
|
|
const dispatch = createEventDispatcher();
|
|
|
|
onMount(() => {
|
|
if (element && autofocus) {
|
|
element.focus();
|
|
}
|
|
});
|
|
|
|
function handleInput() {
|
|
hasFocus = true;
|
|
}
|
|
|
|
function handleKeydown(event: KeyboardEvent) {
|
|
event.stopImmediatePropagation();
|
|
|
|
switch (event.key) {
|
|
case 'Escape':
|
|
hasFocus = false;
|
|
break;
|
|
case 'ArrowDown':
|
|
if (selected === null) {
|
|
selected = 0;
|
|
} else {
|
|
selected = (selected + 1) % options.length;
|
|
}
|
|
break;
|
|
case 'ArrowUp':
|
|
if (selected === null) {
|
|
selected = options.length - 1;
|
|
} else {
|
|
selected = (selected - 1 + options.length) % options.length;
|
|
}
|
|
break;
|
|
case 'Enter':
|
|
if (selected !== null) {
|
|
event.preventDefault();
|
|
selectOption(options[selected]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
function selectOption(option: Option) {
|
|
value = option.value;
|
|
search = option.label;
|
|
// It's not working without this line.
|
|
if (!$$slots.output) {
|
|
element.value = search;
|
|
} else {
|
|
search = '';
|
|
}
|
|
|
|
hasFocus = false;
|
|
dispatch('select', option);
|
|
}
|
|
|
|
function clearOption() {
|
|
search = '';
|
|
value = null;
|
|
!$$slots.output && (element.value = search);
|
|
hasFocus = false;
|
|
dispatch('reset');
|
|
}
|
|
|
|
$: showClearBtn = (hasFocus && search) || value;
|
|
</script>
|
|
|
|
<li
|
|
class="u-position-relative form-item"
|
|
class:u-width-full-line={fullWidth}
|
|
class:u-stretch={stretch}>
|
|
<DropList
|
|
bind:show={hasFocus}
|
|
noStyle
|
|
noArrow
|
|
scrollable
|
|
placement="bottom-end"
|
|
position="static"
|
|
fixed>
|
|
<Label {required} {hideRequired} {optionalText} hide={!showLabel} for={id} {tooltip}>
|
|
{label}
|
|
</Label>
|
|
|
|
<div class="custom-select">
|
|
<div class="input-text-wrapper" style="--amount-of-buttons:2">
|
|
{#if $$slots.output && selectedOption}
|
|
<div
|
|
role="button"
|
|
tabindex="0"
|
|
on:keyup={clickOnEnter}
|
|
on:click={() => {
|
|
if (interactiveOutput) hasFocus = !hasFocus;
|
|
}}>
|
|
<slot name="output" option={selectedOption} />
|
|
</div>
|
|
{:else}
|
|
<input
|
|
type="text"
|
|
class="input-text"
|
|
{placeholder}
|
|
{disabled}
|
|
{required}
|
|
bind:value={search}
|
|
bind:this={element}
|
|
on:focus={() => (hasFocus = true)}
|
|
on:click={() => (hasFocus = true)}
|
|
on:input={handleInput}
|
|
on:keydown={handleKeydown} />
|
|
{/if}
|
|
|
|
<div class="options-list">
|
|
{#if showClearBtn}
|
|
<button
|
|
class="options-list-button"
|
|
aria-label="clear field"
|
|
type="button"
|
|
disabled={!interactiveOutput}
|
|
on:click|preventDefault={clearOption}>
|
|
<span class="icon-x" aria-hidden="true" />
|
|
</button>
|
|
{/if}
|
|
<button
|
|
class="options-list-button"
|
|
type="button"
|
|
disabled={!interactiveOutput}
|
|
on:click={() => (hasFocus = !hasFocus)}>
|
|
<span class="icon-cheveron-down" aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<svelte:fragment slot="list">
|
|
{#each options as option, i}
|
|
<li class="drop-list-item">
|
|
<button
|
|
class="drop-button"
|
|
class:is-selected={selected === i}
|
|
type="button"
|
|
on:click|preventDefault={() => {
|
|
selectOption(option);
|
|
}}>
|
|
<slot {option}>
|
|
<span class="text" data-private>{option.label}</span>
|
|
</slot>
|
|
</button>
|
|
</li>
|
|
{:else}
|
|
<li class="drop-list-item">
|
|
<span class="text">There are no {name} that match your search</span>
|
|
</li>
|
|
{/each}
|
|
</svelte:fragment>
|
|
</DropList>
|
|
</li>
|
|
|
|
<style>
|
|
.form-item :global(.drop) {
|
|
translate: 0 4px;
|
|
}
|
|
|
|
.form-item :global(.drop-section) {
|
|
width: 100%;
|
|
margin-inline: initial;
|
|
max-inline-size: initial;
|
|
}
|
|
</style>
|