glyde/component

Message components: action rows, buttons and select menus.

Two positions, two types. A Component is a top-level entry on a message: today only an action row. A RowChild is what sits inside a row: a button, a select menu. Discord answers 400 to a row nested in a row and to a button at top level, and neither is a value that can be built here.

Types 1, 2, 3, 5, 6, 7 and 8 are modelled. Everything else is dropped from the list on decode, so another bot’s Components V2 message does not sink your MESSAGE_CREATE.

Types

A link button with a custom_id is a 400, so each kind carries only the fields its style allows. Styles 5 and 6 are the link and premium buttons.

pub type ButtonKind {
  ActionButton(
    custom_id: String,
    style: ButtonStyle,
    label: ButtonLabel,
  )
  LinkButton(url: String, label: ButtonLabel)
  PremiumButton(sku_id: id.Id(id.Sku))
}

Constructors

  • ActionButton(
      custom_id: String,
      style: ButtonStyle,
      label: ButtonLabel,
    )
  • LinkButton(url: String, label: ButtonLabel)
  • PremiumButton(sku_id: id.Id(id.Sku))

    Discord supplies the label from the SKU, so this one takes none.

Discord needs a button to say something, so “no label and no emoji” is not a state to have. Text is max 80 characters.

pub type ButtonLabel {
  Text(String)
  Icon(emoji.Emoji)
  TextAndIcon(String, emoji.Emoji)
}

Constructors

A style this build has no name for decodes as None, so an action button carrying one is dropped from its row.

pub type ButtonStyle {
  PrimaryButton
  SecondaryButton
  SuccessButton
  DangerButton
}

Constructors

  • PrimaryButton
  • SecondaryButton
  • SuccessButton
  • DangerButton

A top-level entry in a message’s components. A component type this build does not model is dropped from the list on decode.

pub type Component {
  ActionRow(id: option.Option(Int), components: List(RowChild))
}

Constructors

  • ActionRow(id: option.Option(Int), components: List(RowChild))

    Type 1. Holds up to five buttons, or exactly one select menu.

    Arguments

    id

    Discord’s per-component sequence number, not a snowflake.

Discord’s type number, the one field both positions carry. Types 9 to 23 are Components V2, unmodelled, and a value this build has no name for decodes as None.

pub type ComponentType {
  ActionRowType
  ButtonType
  StringSelectType
  TextInputType
  UserSelectType
  RoleSelectType
  MentionableSelectType
  ChannelSelectType
}

Constructors

  • ActionRowType
  • ButtonType
  • StringSelectType
  • TextInputType

    A modal’s text input, one per action row.

  • UserSelectType
  • RoleSelectType
  • MentionableSelectType
  • ChannelSelectType

A pre-selected entry on a mentionable select. A channel is not one of the two things a mentionable menu picks, so it is not a value here.

pub type MentionableDefault {
  MentionUser(id.Id(id.User))
  MentionRole(id.Id(id.Role))
}

Constructors

What an action row holds. Never a row: Discord answers 400 to a nested one, and never a Components V2 element, which is top level. A child type this build does not model is dropped from the row on decode.

pub type RowChild {
  Button(
    id: option.Option(Int),
    kind: ButtonKind,
    disabled: Bool,
  )
  SelectMenu(
    id: option.Option(Int),
    custom_id: String,
    kind: SelectKind,
    placeholder: option.Option(String),
    min_values: option.Option(Int),
    max_values: option.Option(Int),
    disabled: Bool,
  )
}

Constructors

  • Button(id: option.Option(Int), kind: ButtonKind, disabled: Bool)

    Type 2. What it says is in kind: a premium button takes its label from the SKU, and the other two need one of their own.

  • SelectMenu(
      id: option.Option(Int),
      custom_id: String,
      kind: SelectKind,
      placeholder: option.Option(String),
      min_values: option.Option(Int),
      max_values: option.Option(Int),
      disabled: Bool,
    )

    Types 3, 5, 6, 7 and 8. kind is the discriminator.

    Arguments

    custom_id

    1 to 100 characters, chosen by you.

pub type SelectKind {
  StringSelect(options: List(SelectOption))
  UserSelect(default_users: List(id.Id(id.User)))
  RoleSelect(default_roles: List(id.Id(id.Role)))
  MentionableSelect(default_values: List(MentionableDefault))
  ChannelSelect(
    channel_types: List(channel.ChannelType),
    default_channels: List(id.Id(id.Channel)),
  )
}

Constructors

  • StringSelect(options: List(SelectOption))

    Type 3. Carries 1 to 25 options.

  • UserSelect(default_users: List(id.Id(id.User)))

    Type 5.

  • RoleSelect(default_roles: List(id.Id(id.Role)))

    Type 6.

  • MentionableSelect(default_values: List(MentionableDefault))

    Type 7. Resolves to users and roles together.

  • ChannelSelect(
      channel_types: List(channel.ChannelType),
      default_channels: List(id.Id(id.Channel)),
    )

    Type 8.

    Arguments

    channel_types

    Empty means every channel type is offered.

pub type SelectOption {
  SelectOption(
    label: String,
    value: String,
    description: option.Option(String),
    emoji: option.Option(emoji.Emoji),
    default: Bool,
  )
}

Constructors

  • SelectOption(
      label: String,
      value: String,
      description: option.Option(String),
      emoji: option.Option(emoji.Emoji),
      default: Bool,
    )

    Arguments

    label

    Max 100 characters, shown to the user.

    value

    Max 100 characters, sent back to you on click.

Values

pub fn button(
  custom_id custom_id: String,
  label label: String,
) -> RowChild

PrimaryButton is our default, not Discord’s: the API requires a style and has none of its own. Change it with a record update.

pub fn button_style_from_int(
  value: Int,
) -> option.Option(ButtonStyle)
pub fn button_style_to_int(value: ButtonStyle) -> Int
pub fn button_style_to_json(value: ButtonStyle) -> json.Json
pub fn component_type(component: Component) -> ComponentType
pub fn component_type_from_int(
  value: Int,
) -> option.Option(ComponentType)
pub fn component_type_to_int(value: ComponentType) -> Int
pub fn decoder() -> decode.Decoder(option.Option(Component))

A top-level component that is not an action row is dropped: None here, filtered by wire.known_list_field at the call site. A button at top level is a 400 on the way back out, so it is not decoded into one either.

pub fn link_button(
  url url: String,
  label label: String,
) -> RowChild

Opens a url, and sends no interaction.

pub fn option(
  label label: String,
  value value: String,
) -> SelectOption
pub fn premium_button(sku_id: id.Id(id.Sku)) -> RowChild

A premium upsell. Discord supplies the label from the SKU.

pub fn row_child_decoder() -> decode.Decoder(
  option.Option(RowChild),
)

A child that is not a button or a select menu is dropped from its row. That covers a text input, a nested row, and a button whose fields cannot build a ButtonKind this side would send back.

pub fn row_child_to_json(child: RowChild) -> json.Json
pub fn row_child_type(child: RowChild) -> ComponentType
pub fn rows(children: List(RowChild)) -> List(Component)

Pack loose children into action rows: five buttons to a row, a whole row per select menu, order preserved. A row and a Components V2 element are top level, so they are not inputs here: append them to what this returns.

pub fn select(
  custom_id custom_id: String,
  kind kind: SelectKind,
) -> RowChild
pub fn select_option_decoder() -> decode.Decoder(SelectOption)
pub fn select_option_to_json(item: SelectOption) -> json.Json
pub fn to_json(component: Component) -> json.Json
Search Document