Current section
Files
Jump to
Current section
Files
src/gbr/ui/input.gleam
////
//// 🧑💻 Gleam UI input super element.
////
import gleam/dynamic/decode
import gleam/int
import gleam/option.{type Option, None, Some}
import lustre/attribute as a
import lustre/element
import lustre/element/html
import lustre/event
import gbr/ui/core/el
import gbr/ui/typo
import gbr/ui/core/model.{
type UIAttrs, type UIProperties, type UIRender, type UIRenders,
}
type Input =
UIInput
type Render(a) =
UIInputRender(a)
type OnChange(a) =
fn(String) -> a
type Text =
typo.UITypo
type Attrs =
UIProperties
/// Input super element.
///
pub opaque type UIInput {
UIInput(el: el.UIEl, value: String, label: Option(Text), note: Option(Text))
}
/// Input super element.
///
pub opaque type UIInputRender(a) {
UIInputRender(
in: Input,
inner: UIRenders(a),
onclick: Option(a),
onpaste: Option(a),
oninput: Option(OnChange(a)),
onchange: Option(OnChange(a)),
onkeypress: Option(OnChange(a)),
)
}
/// New input type text super element.
///
pub fn text(id: String) -> Input {
new(id, "text")
}
/// New input type email super element.
///
pub fn email(id: String) -> Input {
new(id, "email")
}
/// New input type password super element.
///
pub fn password(id: String) -> Input {
new(id, "password")
}
/// New input type checkbox super element.
///
pub fn checkbox(id: String) -> Input {
new(id, "checkbox")
|> sr_only()
}
/// New input super element.
///
pub fn new(id: String, kind: String) -> Input {
let el =
el.new(id)
// lustre/attribute.type_(kind)
|> el.att([#("type_", kind)])
UIInput(el:, value: "", label: None, note: None)
}
/// Set element class
///
pub fn class(in: Input, class: String) -> Input {
let el = el.class(in.el, class)
UIInput(..in, el:)
}
/// Set input value
///
pub fn value(in: Input, value: String) -> Input {
UIInput(..in, value:)
}
pub fn label(in: Input, label: Text) -> Input {
UIInput(..in, label: Some(label))
}
pub fn note(in: Input, note: Text) -> Input {
UIInput(..in, note: Some(note))
}
/// Append input class sr-only .
///
pub fn sr_only(in: Input) -> Input {
attrs(in, [#("class", "sr-only")])
}
/// Set input name.
///
pub fn name(in: Input, name: String) -> Input {
attrs(in, [#("name", name)])
}
/// Set input placeholder.
///
pub fn placeholder(in: Input, value: String) -> Input {
attrs(in, [#("placeholder", value)])
}
/// Set input required.
///
pub fn required(in: Input, value: String) -> Input {
attrs(in, [#("required", value)])
}
pub fn kind(in: Input, kind: String) -> Input {
let el = el.att(in.el, [#("type_", kind)])
UIInput(..in, el:)
}
/// Set input length.
///
pub fn max(in: Input, value: Int) -> Input {
length(in, "maxlength", value)
}
pub fn min(in: Input, value: Int) -> Input {
length(in, "minlength", value)
}
pub fn size(in: Input, value: Int) -> Input {
length(in, "size", value)
}
/// New input render at inner.
///
pub fn at(in: Input, attrs: UIAttrs(a), inner: UIRenders(a)) -> Render(a) {
let inner = [html.span(attrs, inner)]
UIInputRender(
in:,
inner:,
onpaste: None,
onkeypress: None,
oninput: None,
onclick: None,
onchange: None,
)
}
/// Set input render event onclick.
///
pub fn on_click(in: Render(a), onclick: a) -> Render(a) {
on_click_opt(in, Some(onclick))
}
/// Set input render event onchange.
///
pub fn on_change(in: Render(a), onchange: fn(String) -> a) -> Render(a) {
on_change_opt(in, Some(onchange))
}
/// Set input render event onclick.
///
pub fn on_paste(in: Render(a), onpaste: a) -> Render(a) {
on_paste_opt(in, Some(onpaste))
}
/// Set input render event onclick.
///
pub fn on_input(in: Render(a), oninput: fn(String) -> a) -> Render(a) {
on_input_opt(in, Some(oninput))
}
/// Set input render event onclick.
///
pub fn on_keypress(in: Render(a), onkeypress: fn(String) -> a) -> Render(a) {
on_keypress_opt(in, Some(onkeypress))
}
pub fn on_click_opt(in: Render(a), onclick: Option(a)) -> Render(a) {
UIInputRender(..in, onclick:)
}
pub fn on_change_opt(
in: Render(a),
onchange: Option(fn(String) -> a),
) -> Render(a) {
UIInputRender(..in, onchange:)
}
pub fn on_paste_opt(in: Render(a), onpaste: Option(a)) -> Render(a) {
UIInputRender(..in, onpaste:)
}
pub fn on_input_opt(
in: Render(a),
oninput: Option(fn(String) -> a),
) -> Render(a) {
UIInputRender(..in, oninput:)
}
pub fn on_keypress_opt(
in: Render(a),
onkeypress: Option(fn(String) -> a),
) -> Render(a) {
UIInputRender(..in, onkeypress:)
}
/// Render input super element to `lustre/element.{type Element}`.
///
pub fn render(at: Render(a)) -> UIRender(a) {
let UIInputRender(
in:,
inner:,
onclick:,
oninput:,
onchange:,
onpaste:,
onkeypress:,
) = at
let UIInput(el:, label:, note:, ..) = in
// events
let onclick =
option.map(onclick, event.on_click)
|> option.unwrap(a.none())
let oninput =
option.map(oninput, event.on_input)
|> option.unwrap(a.none())
let onchange =
option.map(onchange, event.on_change)
|> option.unwrap(a.none())
let onkeypress =
option.map(onkeypress, event.on_keypress)
|> option.unwrap(a.none())
let onpaste =
option.map(onpaste, fn(onpaste) {
event.on("onpaste", decode.success(onpaste))
})
|> option.unwrap(a.none())
// attrs
let id = el.get_id(el)
let label = case label {
Some(label) -> html.label([a.for(id)], [typo.render(label)])
None -> element.none()
}
let attrs = el.attrs(el)
// input
let input =
html.input([onclick, oninput, onchange, onpaste, onkeypress, ..attrs])
let input = case note {
None -> html.div([], [input, ..inner])
Some(note) ->
html.div([a.class("relative")], [input, typo.render(note), ..inner])
}
// label and input
html.div([], [label, input])
}
// PRIVATE
//
fn attrs(in: Input, att: Attrs) -> Input {
let el = el.att(in.el, att)
UIInput(..in, el:)
}
fn length(in: Input, name: String, value: Int) -> Input {
let el = el.att(in.el, [#(name, int.to_string(value))])
UIInput(..in, el:)
}