From 2f00bc4948729de2a68e41b311b121a0b9d892a7 Mon Sep 17 00:00:00 2001 From: twlite <46562212+twlite@users.noreply.github.com> Date: Fri, 10 Oct 2025 19:40:27 +0545 Subject: [PATCH 1/2] feat: add label and poll jsx components --- apps/test-bot/package.json | 3 +- .../src/app/commands/(general)/poll.tsx | 27 +++ .../commands/(interactions)/+middleware.ts | 2 - .../app/commands/(interactions)/prompt.tsx | 47 +++- .../01-action-row.mdx | 0 .../02-button.mdx | 0 .../03-select-menu.mdx | 0 .../04-modal.mdx | 0 .../02-interactive-components/05-poll.mdx | 211 ++++++++++++++++++ .../01-text-display.mdx | 0 .../02-container.mdx | 0 .../03-media-gallery.mdx | 0 .../04-separator.mdx | 0 .../05-file.mdx | 0 .../06-section.mdx | 0 .../03-display-components/07-label.mdx | 187 ++++++++++++++++ .../src/analytics/analytics-engine.ts | 2 +- .../commandkit/src/app/commands/Context.ts | 2 +- .../src/app/events/EventWorkerContext.ts | 2 +- .../src/app/handlers/AppEventsHandler.ts | 2 +- .../src/app/register/CommandRegistrar.ts | 2 +- packages/commandkit/src/cli/app-process.ts | 2 +- packages/commandkit/src/cli/build.ts | 4 +- .../src/components/common/element.ts | 7 +- .../src/components/{v2 => display}/common.ts | 2 +- .../components/{v2 => display}/container.ts | 0 .../src/components/{v2 => display}/file.ts | 0 .../src/components/{v2 => display}/index.ts | 2 + .../src/components/display/label.ts | 43 ++++ .../{v2 => display}/media-gallery.ts | 0 .../commandkit/src/components/display/poll.ts | 132 +++++++++++ .../src/components/{v2 => display}/section.ts | 0 .../components/{v2 => display}/separator.ts | 0 .../{v2 => display}/text-display.ts | 0 packages/commandkit/src/components/index.ts | 27 ++- .../action-row/ActionRow.ts | 0 .../{v1 => interactive}/button/Button.ts | 0 .../{v1 => interactive}/button/ButtonKit.ts | 1 + .../{v1 => interactive}/modal/Modal.ts | 34 +-- .../{v1 => interactive}/modal/ModalKit.ts | 12 +- .../select-menu/ChannelSelectMenuKit.ts | 0 .../select-menu/MentionableSelectMenuKit.ts | 0 .../select-menu/RoleSelectMenuKit.ts | 0 .../select-menu/SelectMenu.ts | 0 .../select-menu/StringSelectMenuKit.ts | 0 .../select-menu/UserSelectMenuKit.ts | 0 .../{v1 => interactive}/select-menu/common.ts | 0 .../commandkit/src/context/environment.ts | 4 +- .../commandkit/src/flags/feature-flags.ts | 2 +- packages/commandkit/src/index.ts | 1 + .../plugin-runtime/CommandKitPluginRuntime.ts | 2 +- packages/commandkit/src/types.ts | 1 - .../utils/{warn-unstable.ts => warning.ts} | 27 +++ pnpm-lock.yaml | 82 ++++--- pnpm-workspace.yaml | 2 +- 55 files changed, 777 insertions(+), 97 deletions(-) create mode 100644 apps/test-bot/src/app/commands/(general)/poll.tsx rename apps/website/docs/guide/04-jsx-components/{02-discord-components-v1 => 02-interactive-components}/01-action-row.mdx (100%) rename apps/website/docs/guide/04-jsx-components/{02-discord-components-v1 => 02-interactive-components}/02-button.mdx (100%) rename apps/website/docs/guide/04-jsx-components/{02-discord-components-v1 => 02-interactive-components}/03-select-menu.mdx (100%) rename apps/website/docs/guide/04-jsx-components/{02-discord-components-v1 => 02-interactive-components}/04-modal.mdx (100%) create mode 100644 apps/website/docs/guide/04-jsx-components/02-interactive-components/05-poll.mdx rename apps/website/docs/guide/04-jsx-components/{03-discord-components-v2 => 03-display-components}/01-text-display.mdx (100%) rename apps/website/docs/guide/04-jsx-components/{03-discord-components-v2 => 03-display-components}/02-container.mdx (100%) rename apps/website/docs/guide/04-jsx-components/{03-discord-components-v2 => 03-display-components}/03-media-gallery.mdx (100%) rename apps/website/docs/guide/04-jsx-components/{03-discord-components-v2 => 03-display-components}/04-separator.mdx (100%) rename apps/website/docs/guide/04-jsx-components/{03-discord-components-v2 => 03-display-components}/05-file.mdx (100%) rename apps/website/docs/guide/04-jsx-components/{03-discord-components-v2 => 03-display-components}/06-section.mdx (100%) create mode 100644 apps/website/docs/guide/04-jsx-components/03-display-components/07-label.mdx rename packages/commandkit/src/components/{v2 => display}/common.ts (78%) rename packages/commandkit/src/components/{v2 => display}/container.ts (100%) rename packages/commandkit/src/components/{v2 => display}/file.ts (100%) rename packages/commandkit/src/components/{v2 => display}/index.ts (78%) create mode 100644 packages/commandkit/src/components/display/label.ts rename packages/commandkit/src/components/{v2 => display}/media-gallery.ts (100%) create mode 100644 packages/commandkit/src/components/display/poll.ts rename packages/commandkit/src/components/{v2 => display}/section.ts (100%) rename packages/commandkit/src/components/{v2 => display}/separator.ts (100%) rename packages/commandkit/src/components/{v2 => display}/text-display.ts (100%) rename packages/commandkit/src/components/{v1 => interactive}/action-row/ActionRow.ts (100%) rename packages/commandkit/src/components/{v1 => interactive}/button/Button.ts (100%) rename packages/commandkit/src/components/{v1 => interactive}/button/ButtonKit.ts (99%) rename packages/commandkit/src/components/{v1 => interactive}/modal/Modal.ts (82%) rename packages/commandkit/src/components/{v1 => interactive}/modal/ModalKit.ts (95%) rename packages/commandkit/src/components/{v1 => interactive}/select-menu/ChannelSelectMenuKit.ts (100%) rename packages/commandkit/src/components/{v1 => interactive}/select-menu/MentionableSelectMenuKit.ts (100%) rename packages/commandkit/src/components/{v1 => interactive}/select-menu/RoleSelectMenuKit.ts (100%) rename packages/commandkit/src/components/{v1 => interactive}/select-menu/SelectMenu.ts (100%) rename packages/commandkit/src/components/{v1 => interactive}/select-menu/StringSelectMenuKit.ts (100%) rename packages/commandkit/src/components/{v1 => interactive}/select-menu/UserSelectMenuKit.ts (100%) rename packages/commandkit/src/components/{v1 => interactive}/select-menu/common.ts (100%) rename packages/commandkit/src/utils/{warn-unstable.ts => warning.ts} (59%) diff --git a/apps/test-bot/package.json b/apps/test-bot/package.json index a42f53aa..51c083e5 100644 --- a/apps/test-bot/package.json +++ b/apps/test-bot/package.json @@ -26,6 +26,7 @@ }, "devDependencies": { "@types/ms": "^2.1.0", + "cross-env": "^10.1.0", "tsx": "^4.7.0" } -} +} \ No newline at end of file diff --git a/apps/test-bot/src/app/commands/(general)/poll.tsx b/apps/test-bot/src/app/commands/(general)/poll.tsx new file mode 100644 index 00000000..bbc2ff6a --- /dev/null +++ b/apps/test-bot/src/app/commands/(general)/poll.tsx @@ -0,0 +1,27 @@ +import { + Poll, + PollQuestion, + PollAnswer, + CommandData, + ChatInputCommand, +} from 'commandkit'; +import { PollData } from 'discord.js'; + +export const command: CommandData = { + name: 'poll', + description: 'Create a poll', +}; + +export const chatInput: ChatInputCommand = async (ctx) => { + const poll: PollData = ( + + What's your favorite color? + Red + Blue + Green + Other + + ); + + await ctx.interaction.reply({ poll }); +}; diff --git a/apps/test-bot/src/app/commands/(interactions)/+middleware.ts b/apps/test-bot/src/app/commands/(interactions)/+middleware.ts index f12a33d9..d86f01f7 100644 --- a/apps/test-bot/src/app/commands/(interactions)/+middleware.ts +++ b/apps/test-bot/src/app/commands/(interactions)/+middleware.ts @@ -4,8 +4,6 @@ import { MessageFlags } from 'discord.js'; export function beforeExecute(ctx: MiddlewareContext) { Logger.info('Pre-command middleware'); - console.log({ isAI: ctx.ai }); - const user = ctx.isInteraction() ? ctx.interaction.user : ctx.message.author; if (ctx.commandName === 'prompt' && user.id === '159985870458322944') { diff --git a/apps/test-bot/src/app/commands/(interactions)/prompt.tsx b/apps/test-bot/src/app/commands/(interactions)/prompt.tsx index db255008..7b0facd5 100644 --- a/apps/test-bot/src/app/commands/(interactions)/prompt.tsx +++ b/apps/test-bot/src/app/commands/(interactions)/prompt.tsx @@ -3,11 +3,14 @@ import { Modal, ShortInput, ParagraphInput, + Label, OnModalKitSubmit, MessageCommandContext, ChatInputCommandContext, + StringSelectMenu, + StringSelectMenuOption, } from 'commandkit'; -import { MessageFlags } from 'discord.js'; +import { ComponentType, MessageFlags } from 'discord.js'; export const command: CommandData = { name: 'prompt', @@ -17,9 +20,10 @@ export const command: CommandData = { const handleSubmit: OnModalKitSubmit = async (interaction, context) => { const name = interaction.fields.getTextInputValue('name'); const description = interaction.fields.getTextInputValue('description'); + const select = interaction.fields.getField('select'); await interaction.reply({ - content: `Name: ${name}\nDescription: ${description}`, + content: `Name: ${name}\nDescription: ${description}\nSelect: ${select}`, flags: MessageFlags.Ephemeral, }); @@ -28,13 +32,38 @@ const handleSubmit: OnModalKitSubmit = async (interaction, context) => { export async function chatInput(ctx: ChatInputCommandContext) { const modal = ( - - - + + + + ); diff --git a/apps/website/docs/guide/04-jsx-components/02-discord-components-v1/01-action-row.mdx b/apps/website/docs/guide/04-jsx-components/02-interactive-components/01-action-row.mdx similarity index 100% rename from apps/website/docs/guide/04-jsx-components/02-discord-components-v1/01-action-row.mdx rename to apps/website/docs/guide/04-jsx-components/02-interactive-components/01-action-row.mdx diff --git a/apps/website/docs/guide/04-jsx-components/02-discord-components-v1/02-button.mdx b/apps/website/docs/guide/04-jsx-components/02-interactive-components/02-button.mdx similarity index 100% rename from apps/website/docs/guide/04-jsx-components/02-discord-components-v1/02-button.mdx rename to apps/website/docs/guide/04-jsx-components/02-interactive-components/02-button.mdx diff --git a/apps/website/docs/guide/04-jsx-components/02-discord-components-v1/03-select-menu.mdx b/apps/website/docs/guide/04-jsx-components/02-interactive-components/03-select-menu.mdx similarity index 100% rename from apps/website/docs/guide/04-jsx-components/02-discord-components-v1/03-select-menu.mdx rename to apps/website/docs/guide/04-jsx-components/02-interactive-components/03-select-menu.mdx diff --git a/apps/website/docs/guide/04-jsx-components/02-discord-components-v1/04-modal.mdx b/apps/website/docs/guide/04-jsx-components/02-interactive-components/04-modal.mdx similarity index 100% rename from apps/website/docs/guide/04-jsx-components/02-discord-components-v1/04-modal.mdx rename to apps/website/docs/guide/04-jsx-components/02-interactive-components/04-modal.mdx diff --git a/apps/website/docs/guide/04-jsx-components/02-interactive-components/05-poll.mdx b/apps/website/docs/guide/04-jsx-components/02-interactive-components/05-poll.mdx new file mode 100644 index 00000000..7c54e776 --- /dev/null +++ b/apps/website/docs/guide/04-jsx-components/02-interactive-components/05-poll.mdx @@ -0,0 +1,211 @@ +--- +title: Poll +--- + +The `Poll` component allows you to create interactive polls in Discord messages. Users can vote on poll questions with multiple answer options, and the results are displayed in real-time. Polls can be open for up to 32 days (768 hours). + +## Basic usage + +```tsx title="src/app/commands/poll-example.tsx" +import { + CommandData, + Poll, + PollQuestion, + PollAnswer, + ChatInputCommand, +} from 'commandkit'; +import { PollData } from 'discord.js'; + +export const command: CommandData = { + name: 'poll', + description: 'Create a poll', +}; + +export const chatInput: ChatInputCommand = async (ctx) => { + const poll: PollData = ( + + What's your favorite programming language? + JavaScript + TypeScript + Python + Rust + + ); + + await ctx.interaction.reply({ poll }); +}; +``` + +## Poll duration + +Set how long the poll should be active (in hours). The duration defaults to 24 hours and can be up to 32 days (768 hours): + +```tsx title="src/app/commands/poll-duration.tsx" +import { + CommandData, + Poll, + PollQuestion, + PollAnswer, + ChatInputCommand, +} from 'commandkit'; +import { PollData } from 'discord.js'; + +export const command: CommandData = { + name: 'quickpoll', + description: 'Create a quick poll', +}; + +export const chatInput: ChatInputCommand = async (ctx) => { + const poll: PollData = ( + + Quick question: Coffee or tea? + Coffee + Tea + + ); + + await ctx.interaction.reply({ poll }); +}; +``` + +For longer polls, you can specify durations up to 32 days: + +```tsx title="src/app/commands/long-poll.tsx" +import { + CommandData, + Poll, + PollQuestion, + PollAnswer, + ChatInputCommand, +} from 'commandkit'; +import { PollData } from 'discord.js'; + +export const command: CommandData = { + name: 'weeklypoll', + description: 'Create a weekly poll', +}; + +export const chatInput: ChatInputCommand = async (ctx) => { + const poll: PollData = ( + + Weekly poll: What should we work on next? + New features + Bug fixes + Documentation + UI improvements + + ); + + await ctx.interaction.reply({ poll }); +}; +``` + +## Multiple choice polls + +Allow users to select multiple answers: + +```tsx title="src/app/commands/multiselect-poll.tsx" +import { + CommandData, + Poll, + PollQuestion, + PollAnswer, + ChatInputCommand, +} from 'commandkit'; +import { PollData } from 'discord.js'; + +export const command: CommandData = { + name: 'multiselect', + description: 'Create a multiple choice poll', +}; + +export const chatInput: ChatInputCommand = async (ctx) => { + const poll: PollData = ( + + Which social media platforms do you use? (Select all that apply) + Facebook + Twitter + Instagram + LinkedIn + TikTok + YouTube + + ); + + await ctx.interaction.reply({ poll }); +}; +``` + +## Poll layout types + +Customize how your poll appears using different layout types: + +```tsx title="src/app/commands/poll-layouts.tsx" +import { + CommandData, + Poll, + PollQuestion, + PollAnswer, + ChatInputCommand, +} from 'commandkit'; +import { PollData, PollLayoutType } from 'discord.js'; + +export const command: CommandData = { + name: 'layoutpoll', + description: 'Create a poll with custom layout', +}; + +export const chatInput: ChatInputCommand = async (ctx) => { + const poll: PollData = ( + + List layout poll + First item + Second item + Third item + + ); + + await ctx.interaction.reply({ poll }); +}; +``` + +## Poll with media + +Add images or other media to your poll questions: + +```tsx title="src/app/commands/media-poll.tsx" +import { + CommandData, + Poll, + PollQuestion, + PollAnswer, + ChatInputCommand, +} from 'commandkit'; +import { PollData } from 'discord.js'; + +export const command: CommandData = { + name: 'mediapoll', + description: 'Create a poll with media', +}; + +export const chatInput: ChatInputCommand = async (ctx) => { + const poll: PollData = ( + + + Choose your favorite logo design + + Design A + Design B + Design C + + ); + + await ctx.interaction.reply({ poll }); +}; +``` diff --git a/apps/website/docs/guide/04-jsx-components/03-discord-components-v2/01-text-display.mdx b/apps/website/docs/guide/04-jsx-components/03-display-components/01-text-display.mdx similarity index 100% rename from apps/website/docs/guide/04-jsx-components/03-discord-components-v2/01-text-display.mdx rename to apps/website/docs/guide/04-jsx-components/03-display-components/01-text-display.mdx diff --git a/apps/website/docs/guide/04-jsx-components/03-discord-components-v2/02-container.mdx b/apps/website/docs/guide/04-jsx-components/03-display-components/02-container.mdx similarity index 100% rename from apps/website/docs/guide/04-jsx-components/03-discord-components-v2/02-container.mdx rename to apps/website/docs/guide/04-jsx-components/03-display-components/02-container.mdx diff --git a/apps/website/docs/guide/04-jsx-components/03-discord-components-v2/03-media-gallery.mdx b/apps/website/docs/guide/04-jsx-components/03-display-components/03-media-gallery.mdx similarity index 100% rename from apps/website/docs/guide/04-jsx-components/03-discord-components-v2/03-media-gallery.mdx rename to apps/website/docs/guide/04-jsx-components/03-display-components/03-media-gallery.mdx diff --git a/apps/website/docs/guide/04-jsx-components/03-discord-components-v2/04-separator.mdx b/apps/website/docs/guide/04-jsx-components/03-display-components/04-separator.mdx similarity index 100% rename from apps/website/docs/guide/04-jsx-components/03-discord-components-v2/04-separator.mdx rename to apps/website/docs/guide/04-jsx-components/03-display-components/04-separator.mdx diff --git a/apps/website/docs/guide/04-jsx-components/03-discord-components-v2/05-file.mdx b/apps/website/docs/guide/04-jsx-components/03-display-components/05-file.mdx similarity index 100% rename from apps/website/docs/guide/04-jsx-components/03-discord-components-v2/05-file.mdx rename to apps/website/docs/guide/04-jsx-components/03-display-components/05-file.mdx diff --git a/apps/website/docs/guide/04-jsx-components/03-discord-components-v2/06-section.mdx b/apps/website/docs/guide/04-jsx-components/03-display-components/06-section.mdx similarity index 100% rename from apps/website/docs/guide/04-jsx-components/03-discord-components-v2/06-section.mdx rename to apps/website/docs/guide/04-jsx-components/03-display-components/06-section.mdx diff --git a/apps/website/docs/guide/04-jsx-components/03-display-components/07-label.mdx b/apps/website/docs/guide/04-jsx-components/03-display-components/07-label.mdx new file mode 100644 index 00000000..425cbcb0 --- /dev/null +++ b/apps/website/docs/guide/04-jsx-components/03-display-components/07-label.mdx @@ -0,0 +1,187 @@ +--- +title: Label +--- + +The `Label` component wraps modal components with text as a label and optional description, providing better organization and context for form elements. + +## Basic usage + +```tsx title="src/app/commands/label-example.tsx" +import { + CommandData, + Modal, + ShortInput, + Label, + OnModalKitSubmit, + ChatInputCommandContext, +} from 'commandkit'; +import { MessageFlags } from 'discord.js'; + +export const command: CommandData = { + name: 'profile', + description: 'Create your profile', +}; + +const handleSubmit: OnModalKitSubmit = async (interaction, context) => { + const name = interaction.fields.getTextInputValue('name'); + + await interaction.reply({ + content: `Hello, ${name}!`, + flags: MessageFlags.Ephemeral, + }); + + context.dispose(); +}; + +export const chatInput: ChatInputCommandContext = async (ctx) => { + const modal = ( + + + + ); + + await ctx.interaction.showModal(modal); +}; +``` + +## With description + +Labels can include descriptive text to provide additional context: + +```tsx title="src/app/commands/label-description.tsx" +import { + CommandData, + Modal, + ShortInput, + ParagraphInput, + Label, + OnModalKitSubmit, + ChatInputCommandContext, +} from 'commandkit'; +import { MessageFlags } from 'discord.js'; + +export const command: CommandData = { + name: 'profile', + description: 'Create your profile', +}; + +const handleSubmit: OnModalKitSubmit = async (interaction, context) => { + const username = interaction.fields.getTextInputValue('username'); + const bio = interaction.fields.getTextInputValue('bio'); + + await interaction.reply({ + content: `**Username:** ${username}\n**Bio:** ${bio}`, + flags: MessageFlags.Ephemeral, + }); + + context.dispose(); +}; + +export const chatInput: ChatInputCommandContext = async (ctx) => { + const modal = ( + + + + + + ); + + await ctx.interaction.showModal(modal); +}; +``` + +## Multiple labels + +Use multiple labels to organize different sections of your modal: + +```tsx title="src/app/commands/multiple-labels.tsx" +import { + CommandData, + Modal, + ShortInput, + ParagraphInput, + Label, + OnModalKitSubmit, + ChatInputCommandContext, +} from 'commandkit'; +import { MessageFlags } from 'discord.js'; + +export const command: CommandData = { + name: 'contact', + description: 'Send us a message', +}; + +const handleSubmit: OnModalKitSubmit = async (interaction, context) => { + const firstName = interaction.fields.getTextInputValue('firstName'); + const lastName = interaction.fields.getTextInputValue('lastName'); + const email = interaction.fields.getTextInputValue('email'); + const message = interaction.fields.getTextInputValue('message'); + + await interaction.reply({ + content: `**Contact Form Submitted**\n**Name:** ${firstName} ${lastName}\n**Email:** ${email}\n**Message:** ${message}`, + flags: MessageFlags.Ephemeral, + }); + + context.dispose(); +}; + +export const chatInput: ChatInputCommandContext = async (ctx) => { + const modal = ( + + + + + + + + ); + + await ctx.interaction.showModal(modal); +}; +``` diff --git a/packages/commandkit/src/analytics/analytics-engine.ts b/packages/commandkit/src/analytics/analytics-engine.ts index 6ac3882b..a84222f5 100644 --- a/packages/commandkit/src/analytics/analytics-engine.ts +++ b/packages/commandkit/src/analytics/analytics-engine.ts @@ -1,4 +1,4 @@ -import { CommandKit } from '../commandkit'; +import type { CommandKit } from '../commandkit'; import { Logger } from '../logger/Logger'; import { AnalyticsEvent, diff --git a/packages/commandkit/src/app/commands/Context.ts b/packages/commandkit/src/app/commands/Context.ts index 5510b6b1..375d5b50 100644 --- a/packages/commandkit/src/app/commands/Context.ts +++ b/packages/commandkit/src/app/commands/Context.ts @@ -12,7 +12,7 @@ import { TextBasedChannel, UserContextMenuCommandInteraction, } from 'discord.js'; -import { CommandKit } from '../../commandkit'; +import type { CommandKit } from '../../commandkit'; import { GenericFunction, getContext } from '../../context/async-context'; import { CommandKitEnvironment } from '../../context/environment'; import { diff --git a/packages/commandkit/src/app/events/EventWorkerContext.ts b/packages/commandkit/src/app/events/EventWorkerContext.ts index 8ec6e437..9d1cfe8c 100644 --- a/packages/commandkit/src/app/events/EventWorkerContext.ts +++ b/packages/commandkit/src/app/events/EventWorkerContext.ts @@ -1,6 +1,6 @@ import { AsyncLocalStorage } from 'node:async_hooks'; import { ParsedEvent } from '../router'; -import { CommandKit } from '../../commandkit'; +import type { CommandKit } from '../../commandkit'; /** * Context object containing information about the currently executing event. diff --git a/packages/commandkit/src/app/handlers/AppEventsHandler.ts b/packages/commandkit/src/app/handlers/AppEventsHandler.ts index 0f42fddd..2415b44a 100644 --- a/packages/commandkit/src/app/handlers/AppEventsHandler.ts +++ b/packages/commandkit/src/app/handlers/AppEventsHandler.ts @@ -1,5 +1,5 @@ import { Collection } from 'discord.js'; -import { CommandKit } from '../../commandkit'; +import type { CommandKit } from '../../commandkit'; import { ListenerFunction } from '../../events/CommandKitEventsChannel'; import { Logger } from '../../logger/Logger'; import { toFileURL } from '../../utils/resolve-file-url'; diff --git a/packages/commandkit/src/app/register/CommandRegistrar.ts b/packages/commandkit/src/app/register/CommandRegistrar.ts index 3e96969c..5e1afce8 100644 --- a/packages/commandkit/src/app/register/CommandRegistrar.ts +++ b/packages/commandkit/src/app/register/CommandRegistrar.ts @@ -1,5 +1,5 @@ import { ApplicationCommandType, REST, Routes } from 'discord.js'; -import { CommandKit } from '../../commandkit'; +import type { CommandKit } from '../../commandkit'; import { CommandData, CommandMetadata } from '../../types'; import { Logger } from '../../logger/Logger'; diff --git a/packages/commandkit/src/cli/app-process.ts b/packages/commandkit/src/cli/app-process.ts index 92a1199c..4bdaf05c 100644 --- a/packages/commandkit/src/cli/app-process.ts +++ b/packages/commandkit/src/cli/app-process.ts @@ -36,7 +36,7 @@ export function createAppProcess( '--enable-source-maps', ]; - const nodeOptions = process.env.NODE_OPTIONS; + const nodeOptions = process.env.CK_NODE_OPTIONS || process.env.NODE_OPTIONS; let nodeArgs = [...baseArgs]; if (nodeOptions) { diff --git a/packages/commandkit/src/cli/build.ts b/packages/commandkit/src/cli/build.ts index 6c7213fb..d5599d40 100644 --- a/packages/commandkit/src/cli/build.ts +++ b/packages/commandkit/src/cli/build.ts @@ -219,10 +219,10 @@ async function injectEntryFile( const code = `/* Entrypoint File Generated By CommandKit */ ${isDev ? `\n\n// Injected for development\n${wrapInAsyncIIFE([envScript(isDev), emitAntiCrashScript ? antiCrashScript : ''])}\n\n` : wrapInAsyncIIFE([envScript(isDev)])} -import { commandkit } from 'commandkit'; -import { Client } from 'discord.js'; async function bootstrap() { + const { Client } = await import('discord.js'); + const { commandkit } = await import('commandkit'); const app = await import('./app.js').then((m) => m.default ?? m); if (!app || !(app instanceof Client)) { diff --git a/packages/commandkit/src/components/common/element.ts b/packages/commandkit/src/components/common/element.ts index 9f2b6de0..2c9afc1c 100644 --- a/packages/commandkit/src/components/common/element.ts +++ b/packages/commandkit/src/components/common/element.ts @@ -1,7 +1,6 @@ -import { ActionRowBuilder, TextInputBuilder } from 'discord.js'; -import type { ButtonKit } from '../v1/button/ButtonKit'; -import { warnUnstable } from '../../utils/warn-unstable'; -import { ModalKit } from '../v1/modal/ModalKit'; +import type { ActionRowBuilder, TextInputBuilder } from 'discord.js'; +import type { ButtonKit } from '../interactive/button/ButtonKit'; +import type { ModalKit } from '../interactive/modal/ModalKit'; /** * Represents the types of elements that can be used in CommandKit. diff --git a/packages/commandkit/src/components/v2/common.ts b/packages/commandkit/src/components/display/common.ts similarity index 78% rename from packages/commandkit/src/components/v2/common.ts rename to packages/commandkit/src/components/display/common.ts index 5f0177b1..8a7b4f19 100644 --- a/packages/commandkit/src/components/v2/common.ts +++ b/packages/commandkit/src/components/display/common.ts @@ -4,7 +4,7 @@ import { ComponentBuilder } from 'discord.js'; * @private */ export function applyId(props: { id?: number }, component: ComponentBuilder) { - if (props.id != null) { + if (props.id != null && 'setId' in component) { component.setId(props.id); } } diff --git a/packages/commandkit/src/components/v2/container.ts b/packages/commandkit/src/components/display/container.ts similarity index 100% rename from packages/commandkit/src/components/v2/container.ts rename to packages/commandkit/src/components/display/container.ts diff --git a/packages/commandkit/src/components/v2/file.ts b/packages/commandkit/src/components/display/file.ts similarity index 100% rename from packages/commandkit/src/components/v2/file.ts rename to packages/commandkit/src/components/display/file.ts diff --git a/packages/commandkit/src/components/v2/index.ts b/packages/commandkit/src/components/display/index.ts similarity index 78% rename from packages/commandkit/src/components/v2/index.ts rename to packages/commandkit/src/components/display/index.ts index 05de7138..56c1d056 100644 --- a/packages/commandkit/src/components/v2/index.ts +++ b/packages/commandkit/src/components/display/index.ts @@ -4,3 +4,5 @@ export * from './media-gallery'; export * from './section'; export * from './separator'; export * from './text-display'; +export * from './label'; +export * from './poll'; diff --git a/packages/commandkit/src/components/display/label.ts b/packages/commandkit/src/components/display/label.ts new file mode 100644 index 00000000..d8dcda8f --- /dev/null +++ b/packages/commandkit/src/components/display/label.ts @@ -0,0 +1,43 @@ +import { + APIComponentInLabel, + LabelBuilder, + type LabelBuilderData, +} from 'discord.js'; +import { applyId } from './common'; + +/** + * The label properties for the label component. + */ +export interface LabelProps + extends Omit { + /** + * The component that will be wrapped by the label component. + */ + children: LabelBuilderData['component'] & {}; +} + +/** + * The label component wraps modal components with text as a label and optional description. + * @param props The label properties. + * @returns The label builder instance. + * @example ```tsx + * import { Label } from 'commandkit'; + * + * const label = ; + * ``` + */ +export function Label(props: LabelProps) { + const { children, id, ...rest } = props; + + const label = new LabelBuilder({ + ...rest, + // channel select menu builder is missing? + component: children as unknown as APIComponentInLabel, + }); + + applyId(props, label); + + return label; +} diff --git a/packages/commandkit/src/components/v2/media-gallery.ts b/packages/commandkit/src/components/display/media-gallery.ts similarity index 100% rename from packages/commandkit/src/components/v2/media-gallery.ts rename to packages/commandkit/src/components/display/media-gallery.ts diff --git a/packages/commandkit/src/components/display/poll.ts b/packages/commandkit/src/components/display/poll.ts new file mode 100644 index 00000000..3604101c --- /dev/null +++ b/packages/commandkit/src/components/display/poll.ts @@ -0,0 +1,132 @@ +import { + PollLayoutType, + type PollAnswerData, + type PollData, + type PollQuestionMedia, +} from 'discord.js'; + +/** + * The poll properties for the poll component. + */ +export interface PollProps + extends Partial> { + /** + * The poll children components (question and answers). + */ + children: PollChildrenType[]; +} + +enum PollChildType { + Answer, + Question, +} + +type PollChild = T & { + $$typeof: Type; +}; + +export type PollChildrenType = + | PollChild + | PollChild; + +/** + * The poll component creates a Discord poll with a question and multiple answer options. + * @param props The poll properties. + * @returns The poll data object. + * @example ```tsx + * import { Poll, PollQuestion, PollAnswer } from 'commandkit'; + * + * const poll = + * What's your favorite color? + * Red + * Blue + * Green + * ; + * ``` + */ +export function Poll({ children, ...props }: PollProps): PollData { + const question = children.find( + (child): child is PollChild => + child.$$typeof === PollChildType.Question, + ); + + if (!question) { + throw new Error('Poll question is required'); + } + + const answers = children.filter( + (child): child is PollChild => + child.$$typeof === PollChildType.Answer, + ); + + const { children: questionChildren, ...questionProps } = question; + const { duration, allowMultiselect, layoutType, ...restProps } = props; + + return { + duration: duration ?? 24, + allowMultiselect: allowMultiselect ?? false, + layoutType: layoutType ?? PollLayoutType.Default, + ...restProps, + question: { text: questionChildren, ...questionProps }, + answers: answers.map( + ({ children: answerChildren, $$typeof, ...answerProps }) => ({ + text: answerChildren, + ...answerProps, + }), + ), + }; +} + +/** + * The poll question properties for the poll question component. + */ +export interface PollQuestionProps extends Omit { + /** + * The question text content. + */ + children: PollQuestionMedia['text']; +} + +/** + * The poll question component defines the question text for a Discord poll. + * @param props The poll question properties. + * @returns The poll question media object. + * @example ```tsx + * import { PollQuestion } from 'commandkit'; + * + * const question = What's your favorite programming language?; + * ``` + */ +export function PollQuestion({ + children, + ...props +}: PollQuestionProps): PollChild { + return { ...props, text: children, $$typeof: PollChildType.Question }; +} + +/** + * The poll answer properties for the poll answer component. + */ +export interface PollAnswerProps extends Omit { + /** + * The answer text content. + */ + children: PollAnswerData['text']; +} + +/** + * The poll answer component defines an answer option for a Discord poll. + * @param props The poll answer properties. + * @returns The poll answer data object. + * @example ```tsx + * import { PollAnswer } from 'commandkit'; + * + * const answer = TypeScript; + * ``` + */ +export function PollAnswer({ + children, + ...props +}: PollAnswerProps): PollChild { + return { ...props, text: children, $$typeof: PollChildType.Answer }; +} diff --git a/packages/commandkit/src/components/v2/section.ts b/packages/commandkit/src/components/display/section.ts similarity index 100% rename from packages/commandkit/src/components/v2/section.ts rename to packages/commandkit/src/components/display/section.ts diff --git a/packages/commandkit/src/components/v2/separator.ts b/packages/commandkit/src/components/display/separator.ts similarity index 100% rename from packages/commandkit/src/components/v2/separator.ts rename to packages/commandkit/src/components/display/separator.ts diff --git a/packages/commandkit/src/components/v2/text-display.ts b/packages/commandkit/src/components/display/text-display.ts similarity index 100% rename from packages/commandkit/src/components/v2/text-display.ts rename to packages/commandkit/src/components/display/text-display.ts diff --git a/packages/commandkit/src/components/index.ts b/packages/commandkit/src/components/index.ts index d0ed748b..68e5ab4a 100644 --- a/packages/commandkit/src/components/index.ts +++ b/packages/commandkit/src/components/index.ts @@ -1,27 +1,26 @@ // action row -export * from './v1/action-row/ActionRow'; +export * from './interactive/action-row/ActionRow'; // buttons -export * from './v1/button/ButtonKit'; -export * from './v1/button/Button'; +export * from './interactive/button/ButtonKit'; +export * from './interactive/button/Button'; // modals -export * from './v1/modal/ModalKit'; -export * from './v1/modal/Modal'; +export * from './interactive/modal/ModalKit'; +export * from './interactive/modal/Modal'; // select menus -export * from './v1/select-menu/StringSelectMenuKit'; -export * from './v1/select-menu/ChannelSelectMenuKit'; -export * from './v1/select-menu/MentionableSelectMenuKit'; -export * from './v1/select-menu/UserSelectMenuKit'; -export * from './v1/select-menu/RoleSelectMenuKit'; -export * from './v1/select-menu/SelectMenu'; -export * from './v1/select-menu/common'; +export * from './interactive/select-menu/StringSelectMenuKit'; +export * from './interactive/select-menu/ChannelSelectMenuKit'; +export * from './interactive/select-menu/MentionableSelectMenuKit'; +export * from './interactive/select-menu/UserSelectMenuKit'; +export * from './interactive/select-menu/RoleSelectMenuKit'; +export * from './interactive/select-menu/SelectMenu'; +export * from './interactive/select-menu/common'; // v2 -export * from './v2/index'; +export * from './display/index'; // common export * from './common/element'; export * from './common/types'; -export * from './common/EventInterceptor'; diff --git a/packages/commandkit/src/components/v1/action-row/ActionRow.ts b/packages/commandkit/src/components/interactive/action-row/ActionRow.ts similarity index 100% rename from packages/commandkit/src/components/v1/action-row/ActionRow.ts rename to packages/commandkit/src/components/interactive/action-row/ActionRow.ts diff --git a/packages/commandkit/src/components/v1/button/Button.ts b/packages/commandkit/src/components/interactive/button/Button.ts similarity index 100% rename from packages/commandkit/src/components/v1/button/Button.ts rename to packages/commandkit/src/components/interactive/button/Button.ts diff --git a/packages/commandkit/src/components/v1/button/ButtonKit.ts b/packages/commandkit/src/components/interactive/button/ButtonKit.ts similarity index 99% rename from packages/commandkit/src/components/v1/button/ButtonKit.ts rename to packages/commandkit/src/components/interactive/button/ButtonKit.ts index 793d218e..af3a1eaa 100644 --- a/packages/commandkit/src/components/v1/button/ButtonKit.ts +++ b/packages/commandkit/src/components/interactive/button/ButtonKit.ts @@ -209,6 +209,7 @@ export class ButtonKit extends ButtonBuilder { } const interceptor = this.#getEventInterceptor(); + if (!interceptor) return; this.#unsub = interceptor.subscribe( Events.InteractionCreate, diff --git a/packages/commandkit/src/components/v1/modal/Modal.ts b/packages/commandkit/src/components/interactive/modal/Modal.ts similarity index 82% rename from packages/commandkit/src/components/v1/modal/Modal.ts rename to packages/commandkit/src/components/interactive/modal/Modal.ts index ec88ca9d..c1c6fb1b 100644 --- a/packages/commandkit/src/components/v1/modal/Modal.ts +++ b/packages/commandkit/src/components/interactive/modal/Modal.ts @@ -1,4 +1,4 @@ -import { ActionRowBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'; +import { TextInputBuilder, TextInputStyle } from 'discord.js'; import { MaybeArray } from '../../common/types'; import { CommandKitElement } from '../../common/element'; import { @@ -8,6 +8,7 @@ import { OnModalKitSubmit, } from './ModalKit'; import { EventInterceptorErrorHandler } from '../../common/EventInterceptor'; +import { warnDeprecated } from '../../../utils/warning'; /** * The properties for the modal component. @@ -15,7 +16,7 @@ import { EventInterceptorErrorHandler } from '../../common/EventInterceptor'; export interface ModalProps { customId?: string; title: string; - children?: MaybeArray; + children?: MaybeArray; onSubmit?: OnModalKitSubmit; onEnd?: OnModalKitEnd; onError?: EventInterceptorErrorHandler; @@ -48,17 +49,11 @@ export function Modal(props: ModalProps): CommandKitElement<'modal'> { } if (props.children) { - const childs = ( - Array.isArray(props.children) ? props.children : [props.children] - ) - .map((c) => { - if (c instanceof ActionRowBuilder) return c; - if (c instanceof TextInputBuilder) - return new ActionRowBuilder().addComponents(c); - }) - .filter((c): c is ActionRowBuilder => c != null); - - modal.addComponents(childs); + const childs = Array.isArray(props.children) + ? props.children + : [props.children]; + + modal.components.push(...childs); } if (props.onEnd) { @@ -74,7 +69,10 @@ export function Modal(props: ModalProps): CommandKitElement<'modal'> { export interface TextInputProps { customId: string; - label: string; + /** + * @deprecated use the `