2025-08-14 19:20:25 +03:00
import type { ComponentChildren } from "preact" ;
2025-08-18 17:37:20 +03:00
import { CSSProperties } from "preact/compat" ;
2025-08-14 19:20:25 +03:00
type OnChangeListener = ( newValue : string ) = > void ;
2025-08-18 11:21:09 +03:00
export interface FormSelectGroup < T > {
2025-08-14 19:20:25 +03:00
title : string ;
items : T [ ] ;
}
interface ValueConfig < T , Q > {
values : Q [ ] ;
/** The property of an item of {@link values} to be used as the key, uniquely identifying it. The key will be passed to the change listener. */
2025-08-14 18:51:32 +03:00
keyProperty : keyof T ;
2025-08-14 19:20:25 +03:00
/** The property of an item of {@link values} to be used as the label, representing a human-readable version of the key. If missing, {@link keyProperty} will be used instead. */
titleProperty? : keyof T ;
/** The current value of the combobox. The value will be looked up by going through {@link values} and looking an item whose {@link #keyProperty} value matches this one */
currentValue? : string ;
2025-08-14 18:18:45 +03:00
}
2025-08-14 19:20:25 +03:00
interface FormSelectProps < T , Q > extends ValueConfig < T , Q > {
2025-08-19 22:54:15 +03:00
id? : string ;
2025-08-14 19:20:25 +03:00
onChange : OnChangeListener ;
2025-08-18 17:37:20 +03:00
style? : CSSProperties ;
2025-08-14 19:20:25 +03:00
}
/ * *
* Combobox component that takes in any object array as data . Each item of the array is rendered as an item , and the key and values are obtained by looking into the object by a specified key .
* /
2025-08-19 22:54:15 +03:00
export default function FormSelect < T > ( { id , onChange , style , . . . restProps } : FormSelectProps < T , T > ) {
2025-08-14 19:20:25 +03:00
return (
2025-08-19 22:54:15 +03:00
< FormSelectBody id = { id } onChange = { onChange } style = { style } >
2025-08-18 17:37:20 +03:00
< FormSelectGroup { ...restProps } / >
2025-08-14 19:20:25 +03:00
< / FormSelectBody >
) ;
}
/ * *
* Similar to { @link FormSelect } , but the top - level elements are actually groups .
* /
2025-08-19 22:54:15 +03:00
export function FormSelectWithGroups < T > ( { id , values , keyProperty , titleProperty , currentValue , onChange } : FormSelectProps < T , FormSelectGroup < T > > ) {
2025-08-14 19:20:25 +03:00
return (
2025-08-19 22:54:15 +03:00
< FormSelectBody id = { id } onChange = { onChange } >
2025-08-14 19:20:25 +03:00
{ values . map ( ( { title , items } ) = > {
return (
< optgroup label = { title } >
< FormSelectGroup values = { items } keyProperty = { keyProperty } titleProperty = { titleProperty } currentValue = { currentValue } / >
< / optgroup >
) ;
} ) }
< / FormSelectBody >
)
}
2025-08-19 22:54:15 +03:00
function FormSelectBody ( { id , children , onChange , style } : { id? : string , children : ComponentChildren , onChange : OnChangeListener , style? : CSSProperties } ) {
2025-08-14 18:18:45 +03:00
return (
< select
2025-08-19 22:54:15 +03:00
id = { id }
2025-08-14 18:18:45 +03:00
class = "form-select"
onChange = { e = > onChange ( ( e . target as HTMLInputElement ) . value ) }
2025-08-18 17:37:20 +03:00
style = { style }
2025-08-14 18:18:45 +03:00
>
2025-08-14 19:20:25 +03:00
{ children }
2025-08-14 18:18:45 +03:00
< / select >
)
2025-08-14 19:20:25 +03:00
}
function FormSelectGroup < T > ( { values , keyProperty , titleProperty , currentValue } : ValueConfig < T , T > ) {
return values . map ( item = > {
return (
< option
value = { item [ keyProperty ] as any }
2025-08-14 23:54:32 +03:00
selected = { item [ keyProperty ] === currentValue }
2025-08-14 19:20:25 +03:00
>
{ item [ titleProperty ? ? keyProperty ] ? ? item [ keyProperty ] as any }
< / option >
) ;
} ) ;
2025-08-14 18:18:45 +03:00
}