feat(components): Add radio group, checkbox group & checkbox components (#4763)

* feat(components): implementation of the brand new modal component types Radio Group, Checkbox Group, Checkbox

* refactor(components): streamline checkbox component transformation and enhance type definitions

* Update packages/bot/src/transformers/reverse/component.ts

Co-authored-by: Fleny <Fleny113@outlook.com>

* Update packages/bot/src/transformers/reverse/component.ts

Co-authored-by: Fleny <Fleny113@outlook.com>

* Update packages/bot/src/transformers/types.ts

Co-authored-by: Fleny <Fleny113@outlook.com>

* Update packages/bot/src/transformers/component.ts

Co-authored-by: Fleny <Fleny113@outlook.com>

* Update packages/bot/src/transformers/component.ts

Co-authored-by: Fleny <Fleny113@outlook.com>

* Update packages/bot/src/transformers/reverse/component.ts

Co-authored-by: Fleny <Fleny113@outlook.com>

* Update packages/bot/src/transformers/reverse/component.ts

Co-authored-by: Fleny <Fleny113@outlook.com>

* Update packages/bot/src/transformers/component.ts

Co-authored-by: Fleny <Fleny113@outlook.com>

* refractor(component): transformation logic and update type definitions for options handling

* Update packages/bot/src/transformers/component.ts

Co-authored-by: Awesome Stickz <awesome@stickz.dev>

* refractor(components): move types of new components below file upload to match discord docs

* Update packages/types/src/discord/components.ts

Co-authored-by: Fleny <Fleny113@outlook.com>

* Update packages/types/src/discordeno/components.ts

Co-authored-by: Fleny <Fleny113@outlook.com>

---------

Co-authored-by: Fleny <Fleny113@outlook.com>
Co-authored-by: Awesome Stickz <awesome@stickz.dev>
This commit is contained in:
adam
2026-02-17 03:27:39 +05:30
committed by GitHub
co-authored by Fleny Awesome Stickz
parent 1c68720b68
commit 70da0ac9ff
6 changed files with 301 additions and 9 deletions
+1
View File
@@ -327,6 +327,7 @@ export function createDesiredPropertiesObject<T extends RecursivePartial<Transfo
style: defaultValue,
label: defaultValue,
value: defaultValue,
default: defaultValue,
emoji: defaultValue,
url: defaultValue,
channelTypes: defaultValue,
@@ -1,6 +1,10 @@
import {
type DiscordActionRow,
type DiscordButtonComponent,
type DiscordCheckboxComponent,
type DiscordCheckboxGroupComponent,
type DiscordCheckboxGroupInteractionResponse,
type DiscordCheckboxInteractionResponse,
type DiscordChannelSelectComponent,
type DiscordChannelSelectInteractionResponseFromModal,
type DiscordContainerComponent,
@@ -15,6 +19,8 @@ import {
type DiscordMentionableSelectInteractionResponseFromModal,
type DiscordMessageComponent,
type DiscordMessageComponentFromModalInteractionResponse,
type DiscordRadioGroupComponent,
type DiscordRadioGroupInteractionResponse,
type DiscordRoleSelectComponent,
type DiscordRoleSelectInteractionResponseFromModal,
type DiscordSectionComponent,
@@ -91,6 +97,15 @@ export function transformComponent(bot: Bot, payload: DiscordMessageComponent |
case MessageComponentTypes.FileUpload:
component = transformFileUploadComponent(bot, payload);
break;
case MessageComponentTypes.RadioGroup:
component = transformRadioGroupComponent(bot, payload);
break;
case MessageComponentTypes.CheckboxGroup:
component = transformCheckboxGroupComponent(bot, payload);
break;
case MessageComponentTypes.Checkbox:
component = transformCheckboxComponent(bot, payload);
break;
}
return bot.transformers.customizers.component(bot, payload, component);
@@ -444,3 +459,57 @@ function transformFileUploadComponent(bot: Bot, payload: DiscordFileUploadCompon
return fileUpload;
}
function transformRadioGroupComponent(bot: Bot, payload: DiscordRadioGroupComponent | DiscordRadioGroupInteractionResponse) {
const props = bot.transformers.desiredProperties.component;
const radioGroup = {} as SetupDesiredProps<Component, TransformersDesiredProperties, DesiredPropertiesBehavior>;
if (props.type && payload.type) radioGroup.type = payload.type;
if (props.id && payload.id) radioGroup.id = payload.id;
if (props.customId && payload.custom_id) radioGroup.customId = payload.custom_id;
// Check if this is the component (has options) or the interaction response (modal submit, has value)
if ('options' in payload) {
if (props.options && payload.options) radioGroup.options = payload.options;
if (props.required && payload.required !== undefined) radioGroup.required = payload.required;
} else {
if (props.value) radioGroup.value = payload.value ?? undefined;
}
return radioGroup;
}
function transformCheckboxGroupComponent(bot: Bot, payload: DiscordCheckboxGroupComponent | DiscordCheckboxGroupInteractionResponse) {
const props = bot.transformers.desiredProperties.component;
const checkboxGroup = {} as SetupDesiredProps<Component, TransformersDesiredProperties, DesiredPropertiesBehavior>;
if (props.type && payload.type) checkboxGroup.type = payload.type;
if (props.id && payload.id) checkboxGroup.id = payload.id;
if (props.customId && payload.custom_id) checkboxGroup.customId = payload.custom_id;
// Check if this is the component (has options) or the interaction response (modal submit, has values)
if ('options' in payload) {
if (props.options && payload.options) checkboxGroup.options = payload.options;
if (props.minValues && payload.min_values !== undefined) checkboxGroup.minValues = payload.min_values;
if (props.maxValues && payload.max_values !== undefined) checkboxGroup.maxValues = payload.max_values;
if (props.required && payload.required !== undefined) checkboxGroup.required = payload.required;
} else {
if (props.values && payload.values) checkboxGroup.values = payload.values;
}
return checkboxGroup;
}
function transformCheckboxComponent(bot: Bot, payload: DiscordCheckboxComponent | DiscordCheckboxInteractionResponse) {
const props = bot.transformers.desiredProperties.component;
const checkbox = {} as SetupDesiredProps<Component, TransformersDesiredProperties, DesiredPropertiesBehavior>;
if (props.type && payload.type) checkbox.type = payload.type;
if (props.id && payload.id) checkbox.id = payload.id;
if (props.customId && payload.custom_id) checkbox.customId = payload.custom_id;
if (props.value && 'value' in payload) checkbox.value = payload.value;
if (props.default && 'default' in payload) checkbox.default = payload.default;
return checkbox;
}
@@ -2,6 +2,8 @@ import {
type ButtonStyles,
type DiscordActionRow,
type DiscordButtonComponent,
type DiscordCheckboxComponent,
type DiscordCheckboxGroupComponent,
type DiscordChannelSelectComponent,
type DiscordChannelSelectInteractionResponseFromModal,
type DiscordContainerComponent,
@@ -14,6 +16,7 @@ import {
type DiscordMentionableSelectInteractionResponseFromModal,
type DiscordMessageComponent,
type DiscordMessageComponentFromModalInteractionResponse,
type DiscordRadioGroupComponent,
type DiscordRoleSelectComponent,
type DiscordRoleSelectInteractionResponseFromModal,
type DiscordSectionComponent,
@@ -27,6 +30,7 @@ import {
type DiscordUserSelectComponent,
type DiscordUserSelectInteractionResponseFromModal,
MessageComponentTypes,
type SelectOption,
type TextStyles,
} from '@discordeno/types';
import type { Bot } from '../../bot.js';
@@ -68,6 +72,12 @@ export function transformComponentToDiscordComponent(
return transformLabelComponent(bot, payload);
case MessageComponentTypes.FileUpload:
return transformFileUploadComponent(bot, payload);
case MessageComponentTypes.RadioGroup:
return transformRadioGroupComponent(bot, payload);
case MessageComponentTypes.CheckboxGroup:
return transformCheckboxGroupComponent(bot, payload);
case MessageComponentTypes.Checkbox:
return transformCheckboxComponent(bot, payload);
case MessageComponentTypes.Separator:
case MessageComponentTypes.TextDisplay:
// As of now they are compatible
@@ -143,7 +153,7 @@ function transformInputTextComponent(_bot: Bot, payload: Component): DiscordText
style: payload.style as TextStyles,
custom_id: payload.customId!,
label: payload.label!,
value: payload.value,
value: payload.value as string | undefined,
max_length: payload.maxLength,
min_length: payload.minLength,
placeholder: payload.placeholder,
@@ -171,7 +181,7 @@ function transformStringSelectMenuComponent(
disabled: payload.disabled,
max_values: payload.maxValues,
min_values: payload.minValues,
options: payload.options?.map((option) => ({
options: (payload.options as SelectOption[] | undefined)?.map((option) => ({
label: option.label,
value: option.value,
description: option.description,
@@ -354,3 +364,34 @@ function transformFileUploadComponent(bot: Bot, payload: Component): DiscordFile
required: payload.required,
};
}
function transformRadioGroupComponent(bot: Bot, payload: Component): DiscordRadioGroupComponent {
return {
type: MessageComponentTypes.RadioGroup,
id: payload.id,
custom_id: payload.customId!,
options: payload.options ?? [],
required: payload.required,
};
}
function transformCheckboxGroupComponent(bot: Bot, payload: Component): DiscordCheckboxGroupComponent {
return {
type: MessageComponentTypes.CheckboxGroup,
id: payload.id,
custom_id: payload.customId!,
options: payload.options ?? [],
min_values: payload.minValues,
max_values: payload.maxValues,
required: payload.required,
};
}
function transformCheckboxComponent(bot: Bot, payload: Component): DiscordCheckboxComponent {
return {
type: MessageComponentTypes.Checkbox,
id: payload.id,
custom_id: payload.customId!,
default: payload.default,
};
}
+8 -3
View File
@@ -13,6 +13,7 @@ import type {
ButtonStyles,
Camelize,
ChannelTypes,
CheckboxGroupOption,
DefaultMessageNotificationLevels,
DiscordActivityInstanceResource,
DiscordActivityLocationKind,
@@ -54,6 +55,7 @@ import type {
PremiumTiers,
PremiumTypes,
PresenceStatus,
RadioGroupOption,
RoleFlags,
ScheduledEventEntityType,
ScheduledEventPrivacyLevel,
@@ -576,8 +578,8 @@ export interface Component {
style?: ButtonStyles | TextStyles;
/** text that appears on the button (max 80 characters) */
label?: string;
/** the dev-define value of the option, max 100 characters for select or 4000 for input. */
value?: string;
/** the dev-define value of the option, max 100 characters for select or 4000 for input; or boolean for checkbox response. */
value?: string | boolean;
/** Emoji object that includes fields of name, id, and animated supporting unicode and custom emojis. */
emoji?: Pick<Partial<Emoji>, 'id' | 'name' | 'animated'>;
/** optional url for link-style buttons that can navigate a user to the web. Only type 5 Link buttons can have a url */
@@ -585,7 +587,8 @@ export interface Component {
/** List of channel types to include in a channel select menu options list */
channelTypes?: ChannelTypes[];
/** The choices! Maximum of 25 items. */
options?: SelectOption[];
/** The string select options or the radio or checkbox group options */
options?: SelectOption[] | RadioGroupOption[] | CheckboxGroupOption[];
/** A custom placeholder text if nothing is selected. Maximum 150 characters. */
placeholder?: string;
/** The minimum number of items that must be selected. Default 1. Between 1-25. */
@@ -632,6 +635,8 @@ export interface Component {
component?: Component;
/** The text of the selected options */
values?: string[];
/** Whether the checkbox is selected by default (Checkbox component). */
default?: boolean;
/** Resolved entities from selected options */
resolved?: InteractionDataResolved;
}