File size: 3,526 Bytes
a02aa32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<script lang="ts">
	import CarbonChevronDown from "~icons/carbon/chevron-down";
	
	interface Props {
		label: string;
		value: string;
		suggestions: string[];
		disabled?: boolean;
		maxlength?: number;
		placeholder?: string;
		onChange: (value: string) => void;
	}
	
	let { label, value, suggestions, disabled = false, maxlength, placeholder = "Type or select...", onChange }: Props = $props();
	
	let showSuggestions = $state(false);
	let inputValue = $state(value);
	let focusedIndex = $state(-1);
	
	// Update inputValue when value prop changes (e.g., when switching personas)
	$effect(() => {
		inputValue = value;
	});
	
	let filteredSuggestions = $derived(
		suggestions.filter(s => 
			s.toLowerCase().includes(inputValue.toLowerCase())
		).slice(0, 10) // Limit to 10 suggestions
	);
	
	function handleInput(e: Event) {
		const target = e.target as HTMLInputElement;
		inputValue = target.value;
		showSuggestions = true;
		focusedIndex = -1;
	}
	
	function selectSuggestion(suggestion: string) {
		inputValue = suggestion;
		showSuggestions = false;
		onChange(suggestion);
	}
	
	function handleBlur() {
		// Delay to allow click on suggestion
		setTimeout(() => {
			showSuggestions = false;
			if (inputValue !== value) {
				onChange(inputValue);
			}
		}, 200);
	}
	
	function handleFocus() {
		if (filteredSuggestions.length > 0) {
			showSuggestions = true;
		}
	}
	
	function handleKeydown(e: KeyboardEvent) {
		if (!showSuggestions || filteredSuggestions.length === 0) return;
		
		if (e.key === 'ArrowDown') {
			e.preventDefault();
			focusedIndex = Math.min(focusedIndex + 1, filteredSuggestions.length - 1);
		} else if (e.key === 'ArrowUp') {
			e.preventDefault();
			focusedIndex = Math.max(focusedIndex - 1, -1);
		} else if (e.key === 'Enter' && focusedIndex >= 0) {
			e.preventDefault();
			selectSuggestion(filteredSuggestions[focusedIndex]);
		} else if (e.key === 'Escape') {
			showSuggestions = false;
		}
	}
</script>

<div class="flex flex-col gap-2 relative">
	<label for={label.toLowerCase().replace(/\s+/g, '-')} class="text-sm font-medium text-gray-700 dark:text-gray-300">
		{label}
	</label>
	
	<div class="relative">
		<input
			id={label.toLowerCase().replace(/\s+/g, '-')}
			type="text"
			value={inputValue}
			{disabled}
			{maxlength}
			{placeholder}
			oninput={handleInput}
			onblur={handleBlur}
			onfocus={handleFocus}
			onkeydown={handleKeydown}
			class="w-full rounded-md border border-gray-300 bg-white px-3 py-2 pr-10 text-sm transition-colors focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 disabled:cursor-not-allowed disabled:opacity-60 dark:border-gray-600 dark:bg-gray-800"
		/>
		{#if !disabled}
			<CarbonChevronDown 
				class="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 transition-transform dark:text-gray-500 {showSuggestions ? 'rotate-180' : ''}"
			/>
		{/if}
	</div>
	
	{#if showSuggestions && filteredSuggestions.length > 0 && !disabled}
		<div class="absolute top-full left-0 right-0 z-40 mt-1 max-h-60 overflow-auto rounded-md border border-gray-300 bg-white shadow-lg dark:border-gray-600 dark:bg-gray-800">
			{#each filteredSuggestions as suggestion, index}
				<button
					type="button"
					class="w-full px-3 py-2 text-left text-sm transition-colors hover:bg-gray-100 dark:hover:bg-gray-700 {index === focusedIndex ? 'bg-gray-100 dark:bg-gray-700' : ''}"
					onclick={() => selectSuggestion(suggestion)}
				>
					{suggestion}
				</button>
			{/each}
		</div>
	{/if}
</div>