package ui import ( "strconv" "git.flowmade.one/flowmade-one/iris/internal/element" "git.flowmade.one/flowmade-one/iris/reactive" ) // TextInput creates a reactive text input field func TextInput(value *reactive.Signal[string], placeholder string) View { input := element.NewElement("input") input.Attr("type", "text") input.Attr("placeholder", placeholder) // Set initial value input.Set("value", value.Get()) // Update input when signal changes reactive.NewEffect(func() { currentValue := value.Get() if input.Get("value").String() != currentValue { input.Set("value", currentValue) } }) // Update signal when user types input.On("input", func() { newValue := input.Get("value").String() if value.Get() != newValue { value.Set(newValue) } }) return View{input} } // TextArea creates a multi-line text input func TextArea(value *reactive.Signal[string], placeholder string, rows int) View { textarea := element.NewElement("textarea") textarea.Attr("placeholder", placeholder) textarea.Attr("rows", string(rune(rows))) // Set initial value textarea.Set("value", value.Get()) // Update textarea when signal changes reactive.NewEffect(func() { currentValue := value.Get() if textarea.Get("value").String() != currentValue { textarea.Set("value", currentValue) } }) // Update signal when user types textarea.On("input", func() { newValue := textarea.Get("value").String() if value.Get() != newValue { value.Set(newValue) } }) return View{textarea} } // Checkbox creates a reactive checkbox input func Checkbox(checked *reactive.Signal[bool], label string) View { container := element.NewElement("label") container.Get("style").Call("setProperty", "display", "flex") container.Get("style").Call("setProperty", "align-items", "center") container.Get("style").Call("setProperty", "gap", "8px") container.Get("style").Call("setProperty", "cursor", "pointer") checkbox := element.NewElement("input") checkbox.Attr("type", "checkbox") // Set initial state if checked.Get() { checkbox.Set("checked", true) } // Update checkbox when signal changes reactive.NewEffect(func() { isChecked := checked.Get() checkbox.Set("checked", isChecked) }) // Update signal when user clicks checkbox.On("change", func() { newValue := checkbox.Get("checked").Bool() checked.Set(newValue) }) // Add label text labelText := element.NewTextNode(label) container.Child(checkbox) container.Child(labelText) return View{container} } // Select creates a dropdown selection component func Select(selected *reactive.Signal[string], options []string, placeholder string) View { selectElem := element.NewElement("select") // Add placeholder option if provided if placeholder != "" { placeholderOption := element.NewElement("option") placeholderOption.Set("value", "") placeholderOption.Set("disabled", true) placeholderOption.Set("selected", true) placeholderOption.Set("textContent", placeholder) selectElem.Child(placeholderOption) } // Add options for _, option := range options { optionElem := element.NewElement("option") optionElem.Set("value", option) optionElem.Set("textContent", option) selectElem.Child(optionElem) } // Set initial value selectElem.Set("value", selected.Get()) // Update select when signal changes reactive.NewEffect(func() { currentValue := selected.Get() selectElem.Set("value", currentValue) }) // Update signal when user selects selectElem.On("change", func() { newValue := selectElem.Get("value").String() selected.Set(newValue) }) return View{selectElem} } // Slider creates a range input for numeric values func Slider(value *reactive.Signal[int], min, max int) View { slider := element.NewElement("input") slider.Attr("type", "range") slider.Attr("min", strconv.Itoa(min)) slider.Attr("max", strconv.Itoa(max)) // Set initial value slider.Set("value", strconv.Itoa(value.Get())) // Update slider when signal changes reactive.NewEffect(func() { currentValue := value.Get() slider.Set("value", strconv.Itoa(currentValue)) }) // Update signal when user drags slider.On("input", func() { newValueStr := slider.Get("value").String() if newValue, err := strconv.Atoi(newValueStr); err == nil { value.Set(newValue) } }) return View{slider} } // NumberInput creates a reactive numeric input field for float64 values func NumberInput(value *reactive.Signal[float64], placeholder string) View { input := element.NewElement("input") input.Attr("type", "number") input.Attr("step", "0.01") input.Attr("min", "0") input.Attr("placeholder", placeholder) // Set initial value if value.Get() != 0 { input.Set("value", strconv.FormatFloat(value.Get(), 'f', -1, 64)) } // Update input when signal changes reactive.NewEffect(func() { currentValue := value.Get() var displayValue string if currentValue == 0 { displayValue = "" } else { displayValue = strconv.FormatFloat(currentValue, 'f', -1, 64) } if input.Get("value").String() != displayValue { input.Set("value", displayValue) } }) // Update signal when user types input.On("input", func() { newValueStr := input.Get("value").String() if newValueStr == "" { value.Set(0.0) } else if newValue, err := strconv.ParseFloat(newValueStr, 64); err == nil { value.Set(newValue) } }) return View{input} }