extonlawrence commited on
Commit
4275aca
·
1 Parent(s): 62ec7ad

Add SelectField component for easier detailed Persona value support

Browse files
Files changed (1) hide show
  1. src/lib/components/SelectField.svelte +102 -0
src/lib/components/SelectField.svelte ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import CarbonChevronDown from "~icons/carbon/chevron-down";
3
+
4
+ const CUSTOM_VALUE = "__custom__";
5
+
6
+ interface Props {
7
+ label: string;
8
+ value: string;
9
+ options: string[];
10
+ disabled?: boolean;
11
+ required?: boolean;
12
+ onChange: (value: string) => void;
13
+ allowCustom?: boolean;
14
+ }
15
+
16
+ let { label, value, options, disabled = false, required = false, onChange, allowCustom = false }: Props = $props();
17
+
18
+ let showCustomInput = $state(false);
19
+ let customValue = $state("");
20
+
21
+ let isCustomValue = $derived(!options.includes(value) && value !== "");
22
+
23
+ function handleSelectChange(e: Event) {
24
+ const target = e.target as HTMLSelectElement;
25
+ const newValue = target.value;
26
+
27
+ if (newValue === CUSTOM_VALUE) {
28
+ showCustomInput = true;
29
+ customValue = value;
30
+ } else {
31
+ showCustomInput = false;
32
+ onChange(newValue);
33
+ }
34
+ }
35
+
36
+ function handleCustomSubmit() {
37
+ if (customValue.trim()) {
38
+ onChange(customValue.trim());
39
+ showCustomInput = false;
40
+ }
41
+ }
42
+
43
+ function handleCustomCancel() {
44
+ showCustomInput = false;
45
+ customValue = "";
46
+ }
47
+ </script>
48
+
49
+ <div class="flex flex-col gap-2">
50
+ <label for={label.toLowerCase().replace(/\s+/g, '-')} class="text-sm font-medium text-gray-700 dark:text-gray-300">
51
+ {label}
52
+ {#if required}<span class="text-red-500">*</span>{/if}
53
+ </label>
54
+
55
+ {#if showCustomInput}
56
+ <div class="flex gap-2">
57
+ <input
58
+ type="text"
59
+ bind:value={customValue}
60
+ 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"
61
+ placeholder="Enter custom value"
62
+ onkeydown={(e) => e.key === 'Enter' && handleCustomSubmit()}
63
+ />
64
+ <button
65
+ type="button"
66
+ 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"
67
+ onclick={handleCustomSubmit}
68
+ >
69
+ Save
70
+ </button>
71
+ <button
72
+ type="button"
73
+ 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"
74
+ onclick={handleCustomCancel}
75
+ >
76
+ Cancel
77
+ </button>
78
+ </div>
79
+ {:else}
80
+ <div class="relative">
81
+ <select
82
+ id={label.toLowerCase().replace(/\s+/g, '-')}
83
+ {disabled}
84
+ {required}
85
+ value={isCustomValue && allowCustom ? CUSTOM_VALUE : value}
86
+ onchange={handleSelectChange}
87
+ 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"
88
+ >
89
+ {#each options as option}
90
+ <option value={option}>{option}</option>
91
+ {/each}
92
+ {#if allowCustom}
93
+ <option value={CUSTOM_VALUE}>
94
+ {isCustomValue ? `Custom: ${value}` : 'Custom...'}
95
+ </option>
96
+ {/if}
97
+ </select>
98
+ <CarbonChevronDown class="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500" />
99
+ </div>
100
+ {/if}
101
+ </div>
102
+