File size: 3,128 Bytes
4275aca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
<script lang="ts">
	import CarbonChevronDown from "~icons/carbon/chevron-down";
	
	const CUSTOM_VALUE = "__custom__";
	
	interface Props {
		label: string;
		value: string;
		options: string[];
		disabled?: boolean;
		required?: boolean;
		onChange: (value: string) => void;
		allowCustom?: boolean;
	}
	
	let { label, value, options, disabled = false, required = false, onChange, allowCustom = false }: Props = $props();
	
	let showCustomInput = $state(false);
	let customValue = $state("");
	
	let isCustomValue = $derived(!options.includes(value) && value !== "");
	
	function handleSelectChange(e: Event) {
		const target = e.target as HTMLSelectElement;
		const newValue = target.value;
		
		if (newValue === CUSTOM_VALUE) {
			showCustomInput = true;
			customValue = value;
		} else {
			showCustomInput = false;
			onChange(newValue);
		}
	}
	
	function handleCustomSubmit() {
		if (customValue.trim()) {
			onChange(customValue.trim());
			showCustomInput = false;
		}
	}
	
	function handleCustomCancel() {
		showCustomInput = false;
		customValue = "";
	}
</script>

<div class="flex flex-col gap-2">
	<label for={label.toLowerCase().replace(/\s+/g, '-')} class="text-sm font-medium text-gray-700 dark:text-gray-300">
		{label}
		{#if required}<span class="text-red-500">*</span>{/if}
	</label>
	
	{#if showCustomInput}
		<div class="flex gap-2">
			<input
				type="text"
				bind:value={customValue}
				class="flex-1 rounded-md border border-gray-300 bg-white px-3 py-2 text-sm transition-colors focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-800"
				placeholder="Enter custom value"
				onkeydown={(e) => e.key === 'Enter' && handleCustomSubmit()}
			/>
			<button
				type="button"
				class="rounded-md bg-black px-3 py-2 text-sm font-medium text-white hover:bg-black/90 dark:bg-white dark:text-black dark:hover:bg-white/90"
				onclick={handleCustomSubmit}
			>
				Save
			</button>
			<button
				type="button"
				class="rounded-md border border-gray-300 px-3 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700"
				onclick={handleCustomCancel}
			>
				Cancel
			</button>
		</div>
	{:else}
		<div class="relative">
			<select
				id={label.toLowerCase().replace(/\s+/g, '-')}
				{disabled}
				{required}
				value={isCustomValue && allowCustom ? CUSTOM_VALUE : value}
				onchange={handleSelectChange}
				class="w-full appearance-none rounded-md border border-gray-300 bg-white px-3 py-2 pr-10 text-sm transition-colors hover:border-gray-400 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"
			>
				{#each options as option}
					<option value={option}>{option}</option>
				{/each}
				{#if allowCustom}
					<option value={CUSTOM_VALUE}>
						{isCustomValue ? `Custom: ${value}` : 'Custom...'}
					</option>
				{/if}
			</select>
			<CarbonChevronDown class="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500" />
		</div>
	{/if}
</div>