From f9ccab9cdaad77a089e5ce21449ff15e04c6b71d Mon Sep 17 00:00:00 2001 From: Filip Hoffmann Date: Fri, 13 Feb 2026 19:17:43 +0100 Subject: [PATCH] add radio group component --- CHANGELOG.md | 3 ++ src/grom/component/radio_group.gleam | 75 ++++++++++++++++++++++++++++ src/grom/interaction.gleam | 26 +++++----- src/grom/modal.gleam | 23 ++------- 4 files changed, 95 insertions(+), 32 deletions(-) create mode 100644 src/grom/component/radio_group.gleam diff --git a/CHANGELOG.md b/CHANGELOG.md index d4071a5..57b54de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +* Add Radio Group component (Filip Hoffmann) + # v4.0.0 (20/01/2026) * Fix bug regarding the decoding of current_user.get_guilds (Filip Hoffmann) diff --git a/src/grom/component/radio_group.gleam b/src/grom/component/radio_group.gleam new file mode 100644 index 0000000..bb2a7ef --- /dev/null +++ b/src/grom/component/radio_group.gleam @@ -0,0 +1,75 @@ +import gleam/json.{type Json} +import gleam/list +import gleam/option.{type Option as GOption, None, Some} + +pub type RadioGroup { + RadioGroup( + id: GOption(Int), + custom_id: String, + options: List(Option), + /// Defaults to True. + is_required: Bool, + ) +} + +pub type Option { + Option( + /// Dev facing value, max 100 characters + value: String, + /// User facing text, max 100 characters + label: String, + /// Max 100 characters + description: GOption(String), + is_default_selected: Bool, + ) +} + +@internal +pub fn to_json(group: RadioGroup) -> Json { + let type_ = [#("type", json.int(21))] + + let id = case group.id { + Some(id) -> [#("id", json.int(id))] + None -> [] + } + + let custom_id = [#("custom_id", json.string(group.custom_id))] + + let options = [#("options", json.array(group.options, option_to_json))] + + let is_required = [#("required", json.bool(group.is_required))] + + [type_, id, custom_id, options, is_required] + |> list.flatten + |> json.object +} + +fn option_to_json(option: Option) -> Json { + let value = [#("value", json.string(option.value))] + + let label = [#("label", json.string(option.label))] + + let description = case option.description { + Some(description) -> [#("description", json.string(description))] + None -> [] + } + + let is_default_selected = [ + #("default", json.bool(option.is_default_selected)), + ] + + [value, label, description, is_default_selected] + |> list.flatten + |> json.object +} + +pub fn new( + custom_id custom_id: String, + options options: List(Option), +) -> RadioGroup { + RadioGroup(None, custom_id, options, True) +} + +pub fn new_option(named label: String, value value: String) -> Option { + Option(value, label, None, False) +} diff --git a/src/grom/interaction.gleam b/src/grom/interaction.gleam index 926e9b1..683ce51 100644 --- a/src/grom/interaction.gleam +++ b/src/grom/interaction.gleam @@ -90,6 +90,11 @@ pub type SubmittedModalComponent { TextDisplaySubmitted(TextDisplaySubmission) LabelSubmitted(LabelSubmission) FileUploadSubmitted(FileUploadSubmission) + RadioGroupSubmitted(RadioGroupSubmission) +} + +pub type RadioGroupSubmission { + RadioGroupSubmission(id: Int, custom_id: String, value: Option(String)) } pub type ButtonExecution { @@ -597,6 +602,7 @@ pub fn submitted_modal_component_decoder() -> decode.Decoder( 10 -> decode.map(text_display_submission_decoder(), TextDisplaySubmitted) 18 -> decode.map(label_submission_decoder(), LabelSubmitted) 19 -> decode.map(file_upload_submission_decoder(), FileUploadSubmitted) + 21 -> decode.map(radio_group_submission_decoder(), RadioGroupSubmitted) _ -> decode.failure( StringSelectSubmitted(StringSelectExecution("", [], None)), @@ -605,6 +611,14 @@ pub fn submitted_modal_component_decoder() -> decode.Decoder( } } +fn radio_group_submission_decoder() -> decode.Decoder(RadioGroupSubmission) { + use id <- decode.field("id", decode.int) + use custom_id <- decode.field("custom_id", decode.string) + use value <- decode.field("value", decode.optional(decode.string)) + + decode.success(RadioGroupSubmission(id:, custom_id:, value:)) +} + @internal pub fn text_input_submission_decoder() -> decode.Decoder(TextInputSubmission) { use id <- decode.field("id", decode.int) @@ -1187,18 +1201,6 @@ pub fn response_message_decoder() -> decode.Decoder(ResponseMessage) { )) } -@internal -pub fn modal_response_decoder() -> decode.Decoder(ModalResponse) { - use custom_id <- decode.field("custom_id", decode.string) - use title <- decode.field("title", decode.string) - use components <- decode.field( - "components", - decode.list(of: modal.component_decoder()), - ) - - decode.success(ModalResponse(custom_id:, title:, components:)) -} - // ENCODERS -------------------------------------------------------------------- @internal diff --git a/src/grom/modal.gleam b/src/grom/modal.gleam index 75d4655..22dbf04 100644 --- a/src/grom/modal.gleam +++ b/src/grom/modal.gleam @@ -1,9 +1,9 @@ -import gleam/dynamic/decode import gleam/json.{type Json} import grom/component/channel_select.{type ChannelSelect} import grom/component/file_upload.{type FileUpload} import grom/component/label.{type Label} import grom/component/mentionable_select.{type MentionableSelect} +import grom/component/radio_group.{type RadioGroup} import grom/component/role_select.{type RoleSelect} import grom/component/string_select.{type StringSelect} import grom/component/text_display.{type TextDisplay} @@ -24,25 +24,7 @@ pub type Component { FileUpload(FileUpload) TextDisplay(TextDisplay) Label(Label) -} - -// DECODERS -------------------------------------------------------------------- - -@internal -pub fn component_decoder() -> decode.Decoder(Component) { - use type_ <- decode.field("type", decode.int) - case type_ { - 3 -> decode.map(string_select.decoder(), StringSelect) - 4 -> decode.map(text_input.decoder(), TextInput) - 5 -> decode.map(user_select.decoder(), UserSelect) - 6 -> decode.map(role_select.decoder(), RoleSelect) - 7 -> decode.map(mentionable_select.decoder(), MentionableSelect) - 8 -> decode.map(channel_select.decoder(), ChannelSelect) - 10 -> decode.map(text_display.decoder(), TextDisplay) - 18 -> decode.map(label.decoder(), Label) - 19 -> decode.map(file_upload.decoder(), FileUpload) - _ -> decode.failure(FileUpload(file_upload.new("")), "Component") - } + RadioGroup(RadioGroup) } // ENCODERS -------------------------------------------------------------------- @@ -60,5 +42,6 @@ pub fn component_to_json(component: Component) -> Json { FileUpload(file_upload) -> file_upload.to_json(file_upload) TextDisplay(text_display) -> text_display.to_json(text_display) Label(label) -> label.to_json(label) + RadioGroup(radio_group) -> radio_group.to_json(radio_group) } } -- 2.51.2