aboutsummaryrefslogtreecommitdiff
path: root/frontend/ts/src/lib/form.ts
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/ts/src/lib/form.ts')
-rw-r--r--frontend/ts/src/lib/form.ts175
1 files changed, 175 insertions, 0 deletions
diff --git a/frontend/ts/src/lib/form.ts b/frontend/ts/src/lib/form.ts
new file mode 100644
index 0000000..a74ddca
--- /dev/null
+++ b/frontend/ts/src/lib/form.ts
@@ -0,0 +1,175 @@
+import { h, Html, Rx, RxAble } from 'lib/rx'
+import * as rx from 'lib/rx'
+import * as L from 'lib/loadable'
+import * as icons from 'lib/icons'
+
+interface InputParams {
+ label: string
+ type?: string
+ initValue?: string
+ select?: boolean
+ onUpdate: (value: string) => void
+ required?: boolean
+}
+
+export function input({ label, type, initValue, select, onUpdate, required }: InputParams): Html {
+ return h('label',
+ { className: 'g-Label' },
+ label,
+ h('input',
+ {
+ type: type ?? 'text',
+ className: 'g-Input',
+ onmount: (element: HTMLInputElement) => {
+ if (select) {
+ element.select()
+ }
+ },
+ oninput: (event: Event) => {
+ let value = (event.target as HTMLInputElement).value
+ onUpdate(type == 'password' ? value : value.trim())
+ },
+ value: initValue,
+ required
+ }
+ )
+ )
+}
+
+interface TextareaParams {
+ label: string
+ initValue?: string
+ select?: boolean
+ onUpdate: (value: string) => void
+ required?: boolean
+}
+
+export function textarea({ label, initValue, select, onUpdate, required }: TextareaParams): Html {
+ return h('label',
+ { className: 'g-Label' },
+ label,
+ h('textarea',
+ {
+ className: 'g-Textarea',
+ onmount: (element: HTMLInputElement) => {
+ if (select) {
+ element.select()
+ }
+ },
+ oninput: (event: Event) => onUpdate((event.target as HTMLInputElement).value.trim()),
+ value: initValue,
+ required
+ }
+ )
+ )
+}
+
+interface SelectParams {
+ label: string
+ initValue: string
+ values: { [key: string]: string }
+ onUpdate: (value: string) => void
+ required?: boolean
+}
+
+export function select({ label, initValue, values, onUpdate, required }: SelectParams): Html {
+ let keys = Object.keys(values)
+ keys.sort((a, b) => values[a].localeCompare(values[b]))
+
+ return h('label',
+ { className: 'g-Label' },
+ label,
+ h('select',
+ {
+ className: 'g-Select',
+ onchange: (event: Event) => {
+ const element = event.target as HTMLSelectElement
+ onUpdate(element.value)
+ },
+ required
+ },
+ h('option', { label: ' ' }),
+ keys.map(key =>
+ h('option',
+ {
+ value: key,
+ selected: initValue == key
+ },
+ values[key]
+ )
+ )
+ )
+ )
+}
+
+export function error(requestVar: Rx<L.Loadable<any>>): Html {
+ return requestVar.map(l =>
+ L.isFailure(l)
+ ? h('div', { className: 'g-FormError' }, l.error)
+ : undefined
+ )
+}
+
+interface SubmitParams {
+ label: string
+ className?: string
+ disabled?: RxAble<boolean>
+ requestVar?: Rx<L.Loadable<any>>
+}
+
+export function submit({ label, className, disabled, requestVar }: SubmitParams): Html {
+ const loadingClassname = requestVar
+ ? requestVar.map(l => L.isLoading(l) ? 'g-Button--Loading' : '')
+ : rx.pure('')
+
+ return h('div',
+ { className: 'g-Form__SubmitParent' },
+ h('input', {
+ type: 'submit',
+ className: loadingClassname.map(lc => `g-Button g-Button--Primary ${className ?? ''} ${lc}`),
+ value: label,
+ disabled
+ }),
+ loadingClassname.map(l =>
+ l && h('div',
+ { className: 'g-Form__SubmitSpinner' },
+ icons.spinner()
+ )
+ )
+ )
+}
+
+interface ButtonParams {
+ style?: string
+ label: string
+ className?: string
+ disabled?: RxAble<boolean>
+ requestVar?: Rx<L.Loadable<any>>
+ onClick: () => void,
+}
+
+export function button({ style, label, className, disabled, requestVar, onClick }: ButtonParams): Html {
+ const loadingClassname = requestVar
+ ? requestVar.map(l => L.isLoading(l) ? 'g-Button--Loading' : '')
+ : rx.pure('')
+
+ return h('div',
+ { className: 'g-Form__SubmitParent' },
+ h('input',
+ {
+ type: 'button',
+ style,
+ className: loadingClassname.map(lc => `g-Button g-Button--Primary ${className ?? ''} ${lc}`),
+ disabled,
+ onclick: () => onClick(),
+ value: label
+ }
+ ),
+ loadingClassname.map(l =>
+ l && h('div',
+ { className: 'g-Form__SubmitSpinner' },
+ icons.spinner()
+ )
+ )
+ )
+}