this repo has no description
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

flush out the rest of the widgets

add tests verifying lustre and nakai builders have the same output for widgets.

+951 -95
+1 -2
formz/src/formz/formz_pipes.gleam
··· 3 3 // date time handling https://hexdocs.pm/birl/index.html 4 4 5 5 // TODO 6 - // - hidden fields 7 - // - list fields 8 6 // - form sets 9 7 // - decoders/toy.decoder? 10 8 // - csrf token 11 9 // - overwrite error messages 12 10 // - emit warning on duplicate named fields 11 + // - clean names so snake_case? 13 12 14 13 import formz/field.{type Field} 15 14 import formz/input.{type Input, Input}
+8 -4
formz/src/formz/string_generator/fields.gleam
··· 3 3 import gleam/list 4 4 5 5 pub fn text_field() { 6 - field.text_field(widgets.text_widget()) 6 + field.text_field(widgets.text_like_widget("text")) 7 7 } 8 8 9 9 pub fn email_field() { 10 - field.email_field(widgets.text_widget()) 10 + field.email_field(widgets.text_like_widget("email")) 11 11 } 12 12 13 13 pub fn integer_field() { 14 - field.integer_field(widgets.text_widget()) 14 + field.integer_field(widgets.text_like_widget("number")) 15 15 } 16 16 17 17 pub fn number_field() { 18 - field.number_field(widgets.text_widget()) 18 + field.number_field(widgets.text_like_widget("number")) 19 19 } 20 20 21 21 pub fn boolean_field() { ··· 24 24 25 25 pub fn enum_field(variants: List(#(String, enum))) { 26 26 field.enum_field(variants, widgets.select_widget(variants)) 27 + } 28 + 29 + pub fn hidden_field() { 30 + field.text_field(widgets.hidden_widget()) |> field.set_hidden(True) 27 31 } 28 32 29 33 pub fn list_field(variants: List(#(String, enum))) {
+2 -10
formz/src/formz/string_generator/simple.gleam
··· 1 1 import formz/formz_use as formz 2 2 import formz/input.{type Input, Input, InvalidInput} 3 + import formz/string_generator/widgets 3 4 import gleam/list 4 5 import gleam/string 5 6 ··· 45 46 } 46 47 47 48 pub fn generate_hidden_field(f: Input(String)) -> String { 48 - case f.hidden { 49 - False -> "" 50 - True -> { 51 - "<input type=\"hidden\" name=\"" 52 - <> f.name 53 - <> "\" value=\"" 54 - <> f.value 55 - <> "\" />" 56 - } 57 - } 49 + widgets.hidden_widget()(f, input.WidgetArgs("", input.Value)) 58 50 }
+50 -13
formz/src/formz/string_generator/widgets.gleam
··· 25 25 input.Id(id) -> " aria-labelledby=\"" <> id <> "\"" 26 26 27 27 // we'll use the label value as the aria-label 28 - input.Value -> 28 + input.Value -> { 29 + let sanitized_label = 30 + label 31 + |> string.replace("\"", "&quot;") 32 + |> string.replace(">", "&gt;") 29 33 case label { 30 34 "" -> "" 31 - _ -> " aria-label=\"" <> label <> "\"" 35 + _ -> " aria-label=\"" <> sanitized_label <> "\"" 32 36 } 37 + } 33 38 } 34 39 } 35 40 ··· 38 43 } 39 44 40 45 fn value_attr(value: String) -> String { 46 + let sanitized_value = 47 + value 48 + |> string.replace("\"", "&quot;") 49 + |> string.replace(">", "&gt;") 41 50 case value { 42 51 "" -> "" 43 - _ -> " value=\"" <> value <> "\"" 52 + _ -> " value=\"" <> sanitized_value <> "\"" 44 53 } 45 54 } 46 55 ··· 62 71 } 63 72 64 73 pub fn password_widget() { 65 - fn(_input: Input(String), _args: WidgetArgs) -> String { 66 - "<input type=\"password\">" 74 + fn(input: Input(String), args: WidgetArgs) -> String { 75 + "<input" 76 + <> type_attr("password") 77 + <> name_attr(input.name) 78 + <> id_attr(args.id) 79 + // <> value_attr(input.value) 80 + <> aria_label_attr(args.labelled_by, input.label) 81 + <> ">" 67 82 } 68 83 } 69 84 70 - pub fn text_widget() { 85 + pub fn text_like_widget(type_: String) { 71 86 fn(input: Input(String), args: WidgetArgs) -> String { 72 87 "<input" 73 - <> type_attr("text") 88 + <> type_attr(type_) 74 89 <> name_attr(input.name) 75 90 <> id_attr(args.id) 76 91 <> value_attr(input.value) ··· 80 95 } 81 96 82 97 pub fn textarea_widget() { 83 - fn(_input: Input(String), _args: WidgetArgs) -> String { 98 + fn(input: Input(String), args: WidgetArgs) -> String { 84 99 // https://chriscoyier.net/2023/09/29/css-solves-auto-expanding-textareas-probably-eventually/ 85 100 // https://til.simonwillison.net/css/resizing-textarea 86 - "<textarea></textarea>" 101 + "<textarea" 102 + <> name_attr(input.name) 103 + <> id_attr(args.id) 104 + <> aria_label_attr(args.labelled_by, input.label) 105 + <> ">" 106 + <> input.value 107 + <> "</textarea>" 108 + } 109 + } 110 + 111 + pub fn hidden_widget() { 112 + fn(input: Input(String), _args: WidgetArgs) -> String { 113 + "<input" 114 + <> type_attr("hidden") 115 + <> name_attr(input.name) 116 + <> value_attr(input.value) 117 + <> ">" 87 118 } 88 119 } 89 120 90 121 pub fn select_widget(variants: List(#(String, value))) { 91 - fn(input: Input(String), _args: WidgetArgs) { 122 + fn(input: Input(String), args: WidgetArgs) { 92 123 let choices = 93 124 list.map(variants, fn(variant) { 94 125 let val = string.inspect(variant.1) 95 - let selected = case input.value == val { 126 + let selected_attr = case input.value == val { 96 127 True -> " selected" 97 128 _ -> "" 98 129 } 99 - { "<option value=\"" <> val <> "\"" <> selected <> ">" } 130 + { "<option" <> value_attr(val) <> selected_attr <> ">" } 100 131 <> variant.0 101 132 <> "</option>" 102 133 }) 103 134 |> string.join("") 104 135 105 - { "<select " <> { " name=\"" <> input.name <> "\"" } <> ">" } 136 + { 137 + "<select" 138 + <> name_attr(input.name) 139 + <> id_attr(args.id) 140 + <> aria_label_attr(args.labelled_by, input.label) 141 + <> ">" 142 + } 106 143 <> choices 107 144 <> { "</select>" } 108 145 }
+8 -4
formz_lustre/src/formz_lustre/fields.gleam
··· 3 3 import gleam/list 4 4 5 5 pub fn text_field() { 6 - field.text_field(widgets.text_widget()) 6 + field.text_field(widgets.text_like_widget("text")) 7 7 } 8 8 9 9 pub fn email_field() { 10 - field.email_field(widgets.text_widget()) 10 + field.email_field(widgets.text_like_widget("email")) 11 11 } 12 12 13 13 pub fn integer_field() { 14 - field.integer_field(widgets.text_widget()) 14 + field.integer_field(widgets.text_like_widget("number")) 15 15 } 16 16 17 17 pub fn number_field() { 18 - field.number_field(widgets.text_widget()) 18 + field.number_field(widgets.text_like_widget("number")) 19 19 } 20 20 21 21 pub fn boolean_field() { ··· 30 30 let keys_indexed = list.index_map(variants, fn(t, i) { #(t.0, i) }) 31 31 field.list_field(variants, widgets.select_widget(keys_indexed)) 32 32 } 33 + 34 + pub fn hidden_field() { 35 + field.text_field(widgets.text_widget()) |> field.set_hidden(True) 36 + }
+96 -20
formz_lustre/src/formz_lustre/widgets.gleam
··· 1 - import formz/input.{type Input} 1 + import formz/input.{type Input, type WidgetArgs, WidgetArgs} 2 2 import gleam/list 3 3 import gleam/string 4 4 import lustre/attribute 5 5 import lustre/element 6 6 import lustre/element/html 7 7 8 + fn id_attr(id: String) -> attribute.Attribute(msg) { 9 + case id { 10 + "" -> attribute.none() 11 + _ -> attribute.id(id) 12 + } 13 + } 14 + 15 + fn name_attr(name: String) -> attribute.Attribute(msg) { 16 + case name { 17 + "" -> attribute.none() 18 + _ -> attribute.name(name) 19 + } 20 + } 21 + 22 + fn aria_label_attr( 23 + labelled_by: input.InputLabelled, 24 + label: String, 25 + ) -> attribute.Attribute(msg) { 26 + case labelled_by { 27 + input.Element -> attribute.none() 28 + input.Id(id) -> attribute.attribute("aria-labelledby", id) 29 + input.Value -> 30 + case label { 31 + "" -> attribute.none() 32 + _ -> attribute.attribute("aria-label", label) 33 + } 34 + } 35 + } 36 + 37 + fn value_attr(value: String) -> attribute.Attribute(msg) { 38 + case value { 39 + "" -> attribute.none() 40 + _ -> attribute.value(value) 41 + } 42 + } 43 + 8 44 pub fn checkbox_widget() { 9 - fn(input: Input(element.Element(msg)), _) -> element.Element(msg) { 45 + fn(input: Input(element.Element(msg)), args: input.WidgetArgs) -> element.Element( 46 + msg, 47 + ) { 10 48 html.input([ 11 49 attribute.type_("checkbox"), 12 - attribute.name(input.name), 13 - attribute.value("1"), 14 - attribute.checked(input.value == "1"), 50 + name_attr(input.name), 51 + id_attr(args.id), 52 + attribute.checked(input.value == "on"), 53 + aria_label_attr(args.labelled_by, input.label), 15 54 ]) 16 55 } 17 56 } 18 57 19 58 pub fn password_widget() { 20 - fn(input: Input(element.Element(msg)), _) -> element.Element(msg) { 59 + fn(input: Input(element.Element(msg)), args: WidgetArgs) -> element.Element( 60 + msg, 61 + ) { 21 62 html.input([ 22 63 attribute.type_("password"), 23 - attribute.name(input.name), 24 - attribute.value(input.value), 64 + name_attr(input.name), 65 + id_attr(args.id), 66 + // value_attr(input.value), 67 + aria_label_attr(args.labelled_by, input.label), 25 68 ]) 26 69 } 27 70 } 28 71 29 72 pub fn text_widget() { 30 - fn(input: Input(element.Element(msg)), _) -> element.Element(msg) { 31 - let placeholder = "" 73 + text_like_widget("text") 74 + } 32 75 76 + pub fn text_like_widget(type_: String) { 77 + fn(input: Input(element.Element(msg)), args: WidgetArgs) -> element.Element( 78 + msg, 79 + ) { 33 80 html.input([ 34 - attribute.type_("text"), 35 - attribute.name(input.name), 36 - attribute.value(input.value), 37 - attribute.placeholder(placeholder), 81 + attribute.type_(type_), 82 + name_attr(input.name), 83 + id_attr(args.id), 84 + value_attr(input.value), 85 + aria_label_attr(args.labelled_by, input.label), 38 86 ]) 39 87 } 40 88 } 41 89 42 90 pub fn textarea_widget() { 43 - fn(input: Input(element.Element(msg)), _) -> element.Element(msg) { 44 - html.textarea([attribute.name(input.name)], "") 91 + fn(input: Input(element.Element(msg)), args: WidgetArgs) -> element.Element( 92 + msg, 93 + ) { 94 + html.textarea( 95 + [ 96 + name_attr(input.name), 97 + id_attr(args.id), 98 + aria_label_attr(args.labelled_by, input.label), 99 + ], 100 + input.value, 101 + ) 45 102 } 46 103 } 47 104 48 105 pub fn hidden_widget() { 49 - fn(input: Input(element.Element(msg)), _) -> element.Element(msg) { 106 + fn(input: Input(element.Element(msg)), _args: WidgetArgs) -> element.Element( 107 + msg, 108 + ) { 50 109 html.input([ 51 110 attribute.type_("hidden"), 52 - attribute.name(input.name), 53 - attribute.value(input.value), 111 + name_attr(input.name), 112 + value_attr(input.value), 54 113 ]) 55 114 } 56 115 } 57 116 58 117 pub fn select_widget(variants: List(#(String, value))) { 59 - fn(input: Input(element.Element(msg)), _) -> element.Element(msg) { 118 + fn(input: Input(element.Element(msg)), args: WidgetArgs) -> element.Element( 119 + msg, 120 + ) { 60 121 html.select( 61 122 [attribute.name(input.name)], 62 123 list.map(variants, fn(variant) { 63 124 let val = string.inspect(variant.1) 64 125 html.option( 65 126 [attribute.value(val), attribute.selected(input.value == val)], 127 + variant.0, 128 + ) 129 + }), 130 + ) 131 + 132 + html.select( 133 + [ 134 + name_attr(input.name), 135 + id_attr(args.id), 136 + aria_label_attr(args.labelled_by, input.label), 137 + ], 138 + list.map(variants, fn(variant) { 139 + let val = string.inspect(variant.1) 140 + html.option( 141 + [value_attr(val), attribute.selected(input.value == val)], 66 142 variant.0, 67 143 ) 68 144 }),
+81
formz_lustre/test/formz_lustre/fields_test.gleam
··· 1 + import formz/field 2 + import gleeunit 3 + import gleeunit/should 4 + 5 + import formz/string_generator/fields as string_fields 6 + import formz_lustre/fields as lustre_fields 7 + 8 + pub fn main() { 9 + gleeunit.main() 10 + } 11 + 12 + fn fields_should_be_equal_except_widget( 13 + field1: field.Field(format1, output), 14 + field2: field.Field(format2, output), 15 + ) { 16 + field1.default |> should.equal(field2.default) 17 + 18 + field1.input.name |> should.equal(field2.input.name) 19 + field1.input.label |> should.equal(field2.input.label) 20 + field1.input.help_text |> should.equal(field2.input.help_text) 21 + field1.input.value |> should.equal(field2.input.value) 22 + field1.input.hidden |> should.equal(field2.input.hidden) 23 + 24 + field1.transform |> should.equal(field2.transform) 25 + } 26 + 27 + pub fn text_field_test() { 28 + let string_field = string_fields.text_field() 29 + let lustre_field = lustre_fields.text_field() 30 + 31 + fields_should_be_equal_except_widget(lustre_field, string_field) 32 + } 33 + 34 + pub fn email_field_test() { 35 + let string_field = string_fields.email_field() 36 + let lustre_field = lustre_fields.email_field() 37 + 38 + fields_should_be_equal_except_widget(lustre_field, string_field) 39 + } 40 + 41 + pub fn number_field_test() { 42 + let string_field = string_fields.number_field() 43 + let lustre_field = lustre_fields.number_field() 44 + 45 + fields_should_be_equal_except_widget(lustre_field, string_field) 46 + } 47 + 48 + pub fn integer_field_test() { 49 + let string_field = string_fields.integer_field() 50 + let lustre_field = lustre_fields.integer_field() 51 + 52 + fields_should_be_equal_except_widget(lustre_field, string_field) 53 + } 54 + 55 + pub fn boolean_field_test() { 56 + let string_field = string_fields.boolean_field() 57 + let lustre_field = lustre_fields.boolean_field() 58 + 59 + fields_should_be_equal_except_widget(lustre_field, string_field) 60 + } 61 + 62 + pub fn hidden_field_test() { 63 + let string_field = string_fields.hidden_field() 64 + let lustre_field = lustre_fields.hidden_field() 65 + 66 + fields_should_be_equal_except_widget(lustre_field, string_field) 67 + } 68 + 69 + pub fn enum_field_test() { 70 + let string_field = string_fields.enum_field([#("a", "A"), #("b", "B")]) 71 + let lustre_field = lustre_fields.enum_field([#("a", "A"), #("b", "B")]) 72 + 73 + fields_should_be_equal_except_widget(lustre_field, string_field) 74 + } 75 + 76 + pub fn list_field_test() { 77 + let string_field = string_fields.list_field([#("a", "A"), #("b", "B")]) 78 + let lustre_field = lustre_fields.list_field([#("a", "A"), #("b", "B")]) 79 + 80 + fields_should_be_equal_except_widget(lustre_field, string_field) 81 + }
+252
formz_lustre/test/formz_lustre/widgets_test.gleam
··· 1 + import formz/input.{WidgetArgs} 2 + import gleeunit 3 + import gleeunit/should 4 + import lustre/element 5 + 6 + import formz/string_generator/widgets as string_widgets 7 + import formz_lustre/widgets 8 + 9 + pub fn main() { 10 + gleeunit.main() 11 + } 12 + 13 + fn convert_to_string(input) { 14 + input 15 + |> element.to_string 16 + } 17 + 18 + fn test_inputs( 19 + name name, 20 + label label, 21 + help help, 22 + hidden hidden, 23 + value value, 24 + args args, 25 + string string_widget, 26 + widget widget, 27 + ) { 28 + let string_input = 29 + input.Input(name, label, help, string_widget, hidden, value) 30 + let input = input.Input(name, label, help, widget, hidden, value) 31 + 32 + input.widget(input, args) 33 + |> convert_to_string 34 + |> should.equal(string_input.widget(string_input, args)) 35 + } 36 + 37 + pub fn text_widget_test() { 38 + test_inputs( 39 + string_widgets.text_like_widget("text"), 40 + widgets.text_like_widget("text"), 41 + name: "a", 42 + label: "A", 43 + help: "help", 44 + hidden: False, 45 + value: "", 46 + args: WidgetArgs("id", labelled_by: input.Element), 47 + ) 48 + 49 + test_inputs( 50 + string_widgets.text_like_widget("text"), 51 + widgets.text_like_widget("text"), 52 + name: "", 53 + label: "A", 54 + help: "help", 55 + hidden: False, 56 + value: "", 57 + args: WidgetArgs("id", labelled_by: input.Element), 58 + ) 59 + 60 + test_inputs( 61 + string_widgets.text_like_widget("text"), 62 + widgets.text_like_widget("text"), 63 + name: "a", 64 + label: "A", 65 + help: "help", 66 + hidden: False, 67 + value: "val", 68 + args: WidgetArgs("id", labelled_by: input.Element), 69 + ) 70 + 71 + test_inputs( 72 + string_widgets.text_like_widget("text"), 73 + widgets.text_like_widget("text"), 74 + name: "a", 75 + label: "A", 76 + help: "help", 77 + hidden: False, 78 + value: "", 79 + args: WidgetArgs("id", labelled_by: input.Value), 80 + ) 81 + } 82 + 83 + pub fn checkbox_widget_test() { 84 + test_inputs( 85 + string_widgets.checkbox_widget(), 86 + widgets.checkbox_widget(), 87 + name: "a", 88 + label: "A", 89 + help: "help", 90 + hidden: False, 91 + value: "", 92 + args: WidgetArgs("id", labelled_by: input.Value), 93 + ) 94 + test_inputs( 95 + string_widgets.checkbox_widget(), 96 + widgets.checkbox_widget(), 97 + name: "a", 98 + label: "A", 99 + help: "help", 100 + hidden: False, 101 + value: "on", 102 + args: WidgetArgs("id", labelled_by: input.Value), 103 + ) 104 + 105 + test_inputs( 106 + string_widgets.checkbox_widget(), 107 + widgets.checkbox_widget(), 108 + name: "a", 109 + label: "A", 110 + help: "help", 111 + hidden: False, 112 + value: "on", 113 + args: WidgetArgs("id", labelled_by: input.Element), 114 + ) 115 + } 116 + 117 + pub fn password_widget_test() { 118 + test_inputs( 119 + string_widgets.password_widget(), 120 + widgets.password_widget(), 121 + name: "a", 122 + label: "A", 123 + help: "help", 124 + hidden: False, 125 + value: "", 126 + args: WidgetArgs("id", labelled_by: input.Value), 127 + ) 128 + test_inputs( 129 + string_widgets.password_widget(), 130 + widgets.password_widget(), 131 + name: "a", 132 + label: "A", 133 + help: "help", 134 + hidden: False, 135 + value: "xxxx", 136 + args: WidgetArgs("id", labelled_by: input.Value), 137 + ) 138 + 139 + test_inputs( 140 + string_widgets.password_widget(), 141 + widgets.password_widget(), 142 + name: "a", 143 + label: "A", 144 + help: "help", 145 + hidden: False, 146 + value: "xxxx", 147 + args: WidgetArgs("id", labelled_by: input.Element), 148 + ) 149 + } 150 + 151 + pub fn textarea_widget_test() { 152 + test_inputs( 153 + string_widgets.textarea_widget(), 154 + widgets.textarea_widget(), 155 + name: "a", 156 + label: "A", 157 + help: "help", 158 + hidden: False, 159 + value: "", 160 + args: WidgetArgs("id", labelled_by: input.Value), 161 + ) 162 + test_inputs( 163 + string_widgets.textarea_widget(), 164 + widgets.textarea_widget(), 165 + name: "a", 166 + label: "A", 167 + help: "help", 168 + hidden: False, 169 + value: "1", 170 + args: WidgetArgs("id", labelled_by: input.Value), 171 + ) 172 + 173 + test_inputs( 174 + string_widgets.textarea_widget(), 175 + widgets.textarea_widget(), 176 + name: "a", 177 + label: "A", 178 + help: "help", 179 + hidden: False, 180 + value: "1", 181 + args: WidgetArgs("id", labelled_by: input.Element), 182 + ) 183 + } 184 + 185 + pub fn hidden_widget_test() { 186 + test_inputs( 187 + string_widgets.hidden_widget(), 188 + widgets.hidden_widget(), 189 + name: "a", 190 + label: "A", 191 + help: "help", 192 + hidden: False, 193 + value: "", 194 + args: WidgetArgs("id", labelled_by: input.Value), 195 + ) 196 + test_inputs( 197 + string_widgets.hidden_widget(), 198 + widgets.hidden_widget(), 199 + name: "a", 200 + label: "A", 201 + help: "help", 202 + hidden: False, 203 + value: "1", 204 + args: WidgetArgs("id", labelled_by: input.Value), 205 + ) 206 + 207 + test_inputs( 208 + string_widgets.hidden_widget(), 209 + widgets.hidden_widget(), 210 + name: "a", 211 + label: "A", 212 + help: "help", 213 + hidden: False, 214 + value: "1", 215 + args: WidgetArgs("id", labelled_by: input.Element), 216 + ) 217 + } 218 + 219 + pub fn select_widget_test() { 220 + let list = [#("One", "a"), #("Two", "b"), #("Three", "c")] 221 + test_inputs( 222 + string_widgets.select_widget(list), 223 + widgets.select_widget(list), 224 + name: "a", 225 + label: "A", 226 + help: "help", 227 + hidden: False, 228 + value: "", 229 + args: WidgetArgs("id", labelled_by: input.Value), 230 + ) 231 + test_inputs( 232 + string_widgets.select_widget(list), 233 + widgets.select_widget(list), 234 + name: "a", 235 + label: "A", 236 + help: "help", 237 + hidden: False, 238 + value: "1", 239 + args: WidgetArgs("id", labelled_by: input.Value), 240 + ) 241 + 242 + test_inputs( 243 + string_widgets.select_widget(list), 244 + widgets.select_widget(list), 245 + name: "a", 246 + label: "A", 247 + help: "help", 248 + hidden: False, 249 + value: "1", 250 + args: WidgetArgs("id", labelled_by: input.Element), 251 + ) 252 + }
+8 -4
formz_nakai/src/formz_nakai/fields.gleam
··· 3 3 import gleam/list 4 4 5 5 pub fn text_field() { 6 - field.text_field(widgets.text_widget()) 6 + field.text_field(widgets.text_like_widget("text")) 7 7 } 8 8 9 9 pub fn email_field() { 10 - field.email_field(widgets.text_widget()) 10 + field.email_field(widgets.text_like_widget("email")) 11 11 } 12 12 13 13 pub fn integer_field() { 14 - field.integer_field(widgets.text_widget()) 14 + field.integer_field(widgets.text_like_widget("number")) 15 15 } 16 16 17 17 pub fn number_field() { 18 - field.number_field(widgets.text_widget()) 18 + field.number_field(widgets.text_like_widget("number")) 19 19 } 20 20 21 21 pub fn boolean_field() { ··· 30 30 let keys_indexed = list.index_map(variants, fn(t, i) { #(t.0, i) }) 31 31 field.list_field(variants, widgets.select_widget(keys_indexed)) 32 32 } 33 + 34 + pub fn hidden_field() { 35 + field.text_field(widgets.hidden_widget()) |> field.set_hidden(True) 36 + }
+102 -38
formz_nakai/src/formz_nakai/widgets.gleam
··· 1 - import formz/input.{type Input} 1 + import formz/input.{type Input, type WidgetArgs} 2 2 import gleam/list 3 3 import gleam/string 4 4 import nakai/attr 5 5 import nakai/html 6 6 7 + fn id_attr(id: String) -> List(attr.Attr) { 8 + case id { 9 + "" -> [] 10 + _ -> [attr.id(id)] 11 + } 12 + } 13 + 14 + fn name_attr(name: String) -> List(attr.Attr) { 15 + case name { 16 + "" -> [] 17 + _ -> [attr.name(name)] 18 + } 19 + } 20 + 21 + fn aria_label_attr( 22 + labelled_by: input.InputLabelled, 23 + label: String, 24 + ) -> List(attr.Attr) { 25 + case labelled_by { 26 + input.Element -> [] 27 + input.Id(id) -> [attr.aria_labelledby(id)] 28 + input.Value -> 29 + case label { 30 + "" -> [] 31 + _ -> [attr.aria_label(label)] 32 + } 33 + } 34 + } 35 + 36 + fn type_attr(type_: String) -> List(attr.Attr) { 37 + [attr.type_(type_)] 38 + } 39 + 40 + fn value_attr(value: String) -> List(attr.Attr) { 41 + case value { 42 + "" -> [] 43 + _ -> [attr.value(value)] 44 + } 45 + } 46 + 7 47 pub fn checkbox_widget() { 8 - fn(input: Input(html.Node), _) -> html.Node { 48 + fn(input: Input(html.Node), args: input.WidgetArgs) -> html.Node { 9 49 let checked_attr = case input.value { 10 - "" -> attr.Attr("x", "x") 11 - _ -> attr.checked() 50 + "on" -> [attr.checked()] 51 + _ -> [] 12 52 } 13 - html.input([ 14 - attr.type_("checkbox"), 15 - attr.name(input.name), 16 - attr.value("1"), 17 - checked_attr, 18 - ]) 53 + 54 + html.input( 55 + list.flatten([ 56 + type_attr("checkbox"), 57 + name_attr(input.name), 58 + id_attr(args.id), 59 + checked_attr, 60 + aria_label_attr(args.labelled_by, input.label), 61 + ]), 62 + ) 19 63 } 20 64 } 21 65 22 66 pub fn password_widget() { 23 - fn(input: Input(html.Node), _) -> html.Node { 24 - html.input([ 25 - attr.type_("password"), 26 - attr.name(input.name), 27 - attr.value(input.value), 28 - ]) 67 + fn(input: Input(html.Node), args: WidgetArgs) -> html.Node { 68 + html.input( 69 + list.flatten([ 70 + type_attr("password"), 71 + name_attr(input.name), 72 + id_attr(args.id), 73 + // value_attr(input.value), 74 + aria_label_attr(args.labelled_by, input.label), 75 + ]), 76 + ) 29 77 } 30 78 } 31 79 32 - pub fn text_widget() { 33 - fn(input: Input(html.Node), _) -> html.Node { 34 - let placeholder = "" 35 - 36 - html.input([ 37 - attr.type_("text"), 38 - attr.name(input.name), 39 - attr.value(input.value), 40 - attr.placeholder(placeholder), 41 - ]) 80 + pub fn text_like_widget(type_: String) { 81 + fn(input: Input(html.Node), args: WidgetArgs) -> html.Node { 82 + html.input( 83 + list.flatten([ 84 + type_attr(type_), 85 + name_attr(input.name), 86 + id_attr(args.id), 87 + value_attr(input.value), 88 + aria_label_attr(args.labelled_by, input.label), 89 + ]), 90 + ) 42 91 } 43 92 } 44 93 45 94 pub fn textarea_widget() { 46 - fn(input: Input(html.Node), _) -> html.Node { 47 - html.textarea([attr.name(input.name)], []) 95 + fn(input: Input(html.Node), args: WidgetArgs) -> html.Node { 96 + html.textarea( 97 + list.flatten([ 98 + name_attr(input.name), 99 + id_attr(args.id), 100 + aria_label_attr(args.labelled_by, input.label), 101 + ]), 102 + [html.Text(input.value)], 103 + ) 48 104 } 49 105 } 50 106 51 107 pub fn hidden_widget() { 52 108 fn(input: Input(html.Node), _) -> html.Node { 53 - html.input([ 54 - attr.type_("hidden"), 55 - attr.name(input.name), 56 - attr.value(input.value), 57 - ]) 109 + html.input( 110 + list.flatten([ 111 + type_attr("hidden"), 112 + name_attr(input.name), 113 + value_attr(input.value), 114 + ]), 115 + ) 58 116 } 59 117 } 60 118 61 119 pub fn select_widget(variants: List(#(String, value))) { 62 - fn(input: Input(html.Node), _) -> html.Node { 120 + fn(input: Input(html.Node), args: WidgetArgs) -> html.Node { 63 121 html.select( 64 - [attr.name(input.name)], 122 + list.flatten([ 123 + name_attr(input.name), 124 + id_attr(args.id), 125 + aria_label_attr(args.labelled_by, input.label), 126 + ]), 65 127 list.map(variants, fn(variant) { 66 128 let val = string.inspect(variant.1) 67 129 let selected_attr = case input.value == val { 68 - True -> attr.selected() 69 - _ -> attr.Attr("x", "x") 130 + True -> [attr.selected()] 131 + _ -> [] 70 132 } 71 - html.option([attr.value(val), selected_attr], [html.Text(variant.0)]) 133 + html.option(list.flatten([value_attr(val), selected_attr]), [ 134 + html.Text(variant.0), 135 + ]) 72 136 }), 73 137 ) 74 138 }
+81
formz_nakai/test/formz_nakai/fields_test.gleam
··· 1 + import formz/field 2 + import gleeunit 3 + import gleeunit/should 4 + 5 + import formz/string_generator/fields as string_fields 6 + import formz_nakai/fields as nakai_fields 7 + 8 + pub fn main() { 9 + gleeunit.main() 10 + } 11 + 12 + fn fields_should_be_equal_except_widget( 13 + field1: field.Field(format1, output), 14 + field2: field.Field(format2, output), 15 + ) { 16 + field1.default |> should.equal(field2.default) 17 + 18 + field1.input.name |> should.equal(field2.input.name) 19 + field1.input.label |> should.equal(field2.input.label) 20 + field1.input.help_text |> should.equal(field2.input.help_text) 21 + field1.input.value |> should.equal(field2.input.value) 22 + field1.input.hidden |> should.equal(field2.input.hidden) 23 + 24 + field1.transform |> should.equal(field2.transform) 25 + } 26 + 27 + pub fn text_field_test() { 28 + let string_field = string_fields.text_field() 29 + let nakai_field = nakai_fields.text_field() 30 + 31 + fields_should_be_equal_except_widget(nakai_field, string_field) 32 + } 33 + 34 + pub fn email_field_test() { 35 + let string_field = string_fields.email_field() 36 + let nakai_field = nakai_fields.email_field() 37 + 38 + fields_should_be_equal_except_widget(nakai_field, string_field) 39 + } 40 + 41 + pub fn number_field_test() { 42 + let string_field = string_fields.number_field() 43 + let nakai_field = nakai_fields.number_field() 44 + 45 + fields_should_be_equal_except_widget(nakai_field, string_field) 46 + } 47 + 48 + pub fn integer_field_test() { 49 + let string_field = string_fields.integer_field() 50 + let nakai_field = nakai_fields.integer_field() 51 + 52 + fields_should_be_equal_except_widget(nakai_field, string_field) 53 + } 54 + 55 + pub fn boolean_field_test() { 56 + let string_field = string_fields.boolean_field() 57 + let nakai_field = nakai_fields.boolean_field() 58 + 59 + fields_should_be_equal_except_widget(nakai_field, string_field) 60 + } 61 + 62 + pub fn hidden_field_test() { 63 + let string_field = string_fields.hidden_field() 64 + let nakai_field = nakai_fields.hidden_field() 65 + 66 + fields_should_be_equal_except_widget(nakai_field, string_field) 67 + } 68 + 69 + pub fn enum_field_test() { 70 + let string_field = string_fields.enum_field([#("a", "A"), #("b", "B")]) 71 + let nakai_field = nakai_fields.enum_field([#("a", "A"), #("b", "B")]) 72 + 73 + fields_should_be_equal_except_widget(nakai_field, string_field) 74 + } 75 + 76 + pub fn list_field_test() { 77 + let string_field = string_fields.list_field([#("a", "A"), #("b", "B")]) 78 + let nakai_field = nakai_fields.list_field([#("a", "A"), #("b", "B")]) 79 + 80 + fields_should_be_equal_except_widget(nakai_field, string_field) 81 + }
+262
formz_nakai/test/formz_nakai/widgets_test.gleam
··· 1 + import formz/input.{type Input, type WidgetArgs, Input, WidgetArgs} 2 + import gleam/string 3 + import gleeunit 4 + import gleeunit/should 5 + import nakai 6 + 7 + import formz/string_generator/widgets as string_widgets 8 + import formz_nakai/widgets 9 + 10 + pub fn main() { 11 + gleeunit.main() 12 + } 13 + 14 + fn remove_self_closing_slash(str: String) -> String { 15 + string.replace(str, " />", ">") 16 + } 17 + 18 + fn remove_checked_true(str: String) -> String { 19 + string.replace(str, "checked=\"true\"", "checked") 20 + } 21 + 22 + fn convert_to_string(input) { 23 + input 24 + |> nakai.to_inline_string 25 + |> remove_self_closing_slash 26 + |> remove_checked_true 27 + } 28 + 29 + fn test_inputs( 30 + name name, 31 + label label, 32 + help help, 33 + hidden hidden, 34 + value value, 35 + args args, 36 + string string_widget, 37 + widget widget, 38 + ) { 39 + let string_input = Input(name, label, help, string_widget, hidden, value) 40 + let input = Input(name, label, help, widget, hidden, value) 41 + 42 + input.widget(input, args) 43 + |> convert_to_string 44 + |> should.equal(string_input.widget(string_input, args)) 45 + } 46 + 47 + pub fn text_widget_test() { 48 + test_inputs( 49 + string_widgets.text_like_widget("text"), 50 + widgets.text_like_widget("text"), 51 + name: "a", 52 + label: "A", 53 + help: "help", 54 + hidden: False, 55 + value: "", 56 + args: WidgetArgs("id", labelled_by: input.Element), 57 + ) 58 + 59 + test_inputs( 60 + string_widgets.text_like_widget("text"), 61 + widgets.text_like_widget("text"), 62 + name: "", 63 + label: "A", 64 + help: "help", 65 + hidden: False, 66 + value: "", 67 + args: WidgetArgs("id", labelled_by: input.Element), 68 + ) 69 + 70 + test_inputs( 71 + string_widgets.text_like_widget("text"), 72 + widgets.text_like_widget("text"), 73 + name: "a", 74 + label: "A", 75 + help: "help", 76 + hidden: False, 77 + value: "val", 78 + args: WidgetArgs("id", labelled_by: input.Element), 79 + ) 80 + 81 + test_inputs( 82 + string_widgets.text_like_widget("text"), 83 + widgets.text_like_widget("text"), 84 + name: "a", 85 + label: "A", 86 + help: "help", 87 + hidden: False, 88 + value: "", 89 + args: WidgetArgs("id", labelled_by: input.Value), 90 + ) 91 + } 92 + 93 + pub fn checkbox_widget_test() { 94 + test_inputs( 95 + string_widgets.checkbox_widget(), 96 + widgets.checkbox_widget(), 97 + name: "a", 98 + label: "A", 99 + help: "help", 100 + hidden: False, 101 + value: "", 102 + args: WidgetArgs("id", labelled_by: input.Id("div")), 103 + ) 104 + test_inputs( 105 + string_widgets.checkbox_widget(), 106 + widgets.checkbox_widget(), 107 + name: "a", 108 + label: "A", 109 + help: "help", 110 + hidden: False, 111 + value: "on", 112 + args: WidgetArgs("id", labelled_by: input.Value), 113 + ) 114 + 115 + test_inputs( 116 + string_widgets.checkbox_widget(), 117 + widgets.checkbox_widget(), 118 + name: "a", 119 + label: "A", 120 + help: "help", 121 + hidden: False, 122 + value: "on", 123 + args: WidgetArgs("id", labelled_by: input.Element), 124 + ) 125 + } 126 + 127 + pub fn password_widget_test() { 128 + test_inputs( 129 + string_widgets.password_widget(), 130 + widgets.password_widget(), 131 + name: "a", 132 + label: "A", 133 + help: "help", 134 + hidden: False, 135 + value: "", 136 + args: WidgetArgs("id", labelled_by: input.Id("div")), 137 + ) 138 + test_inputs( 139 + string_widgets.password_widget(), 140 + widgets.password_widget(), 141 + name: "a", 142 + label: "A", 143 + help: "help", 144 + hidden: False, 145 + value: "xxxx", 146 + args: WidgetArgs("id", labelled_by: input.Value), 147 + ) 148 + 149 + test_inputs( 150 + string_widgets.password_widget(), 151 + widgets.password_widget(), 152 + name: "a", 153 + label: "A", 154 + help: "help", 155 + hidden: False, 156 + value: "xxxx", 157 + args: WidgetArgs("id", labelled_by: input.Element), 158 + ) 159 + } 160 + 161 + pub fn textarea_widget_test() { 162 + test_inputs( 163 + string_widgets.textarea_widget(), 164 + widgets.textarea_widget(), 165 + name: "a", 166 + label: "A", 167 + help: "help", 168 + hidden: False, 169 + value: "", 170 + args: WidgetArgs("id", labelled_by: input.Id("div")), 171 + ) 172 + test_inputs( 173 + string_widgets.textarea_widget(), 174 + widgets.textarea_widget(), 175 + name: "a", 176 + label: "A", 177 + help: "help", 178 + hidden: False, 179 + value: "1", 180 + args: WidgetArgs("id", labelled_by: input.Value), 181 + ) 182 + 183 + test_inputs( 184 + string_widgets.textarea_widget(), 185 + widgets.textarea_widget(), 186 + name: "a", 187 + label: "A", 188 + help: "help", 189 + hidden: False, 190 + value: "1", 191 + args: WidgetArgs("id", labelled_by: input.Element), 192 + ) 193 + } 194 + 195 + pub fn hidden_widget_test() { 196 + test_inputs( 197 + string_widgets.hidden_widget(), 198 + widgets.hidden_widget(), 199 + name: "a", 200 + label: "A", 201 + help: "help", 202 + hidden: False, 203 + value: "", 204 + args: WidgetArgs("id", labelled_by: input.Id("div")), 205 + ) 206 + test_inputs( 207 + string_widgets.hidden_widget(), 208 + widgets.hidden_widget(), 209 + name: "a", 210 + label: "A", 211 + help: "help", 212 + hidden: False, 213 + value: "1", 214 + args: WidgetArgs("id", labelled_by: input.Value), 215 + ) 216 + 217 + test_inputs( 218 + string_widgets.hidden_widget(), 219 + widgets.hidden_widget(), 220 + name: "a", 221 + label: "A", 222 + help: "help", 223 + hidden: False, 224 + value: "1", 225 + args: WidgetArgs("id", labelled_by: input.Element), 226 + ) 227 + } 228 + 229 + pub fn select_widget_test() { 230 + let list = [#("One", "a"), #("Two", "b"), #("Three", "c")] 231 + test_inputs( 232 + string_widgets.select_widget(list), 233 + widgets.select_widget(list), 234 + name: "a", 235 + label: "A", 236 + help: "help", 237 + hidden: False, 238 + value: "", 239 + args: WidgetArgs("id", labelled_by: input.Id("div")), 240 + ) 241 + test_inputs( 242 + string_widgets.select_widget(list), 243 + widgets.select_widget(list), 244 + name: "a", 245 + label: "A", 246 + help: "help", 247 + hidden: False, 248 + value: "1", 249 + args: WidgetArgs("id", labelled_by: input.Value), 250 + ) 251 + 252 + test_inputs( 253 + string_widgets.select_widget(list), 254 + widgets.select_widget(list), 255 + name: "a", 256 + label: "A", 257 + help: "help", 258 + hidden: False, 259 + value: "1", 260 + args: WidgetArgs("id", labelled_by: input.Element), 261 + ) 262 + }