Skip to content

Commit 4be77c2

Browse files
committed
feat: modalkit and modal jsx
1 parent d601434 commit 4be77c2

File tree

8 files changed

+400
-19
lines changed

8 files changed

+400
-19
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import CommandKit, {
2+
SlashCommandProps,
3+
CommandData,
4+
Modal,
5+
ShortInput,
6+
ParagraphInput,
7+
OnModalKitSubmit,
8+
} from 'commandkit';
9+
import { MessageFlags } from 'discord.js';
10+
11+
export const data: CommandData = {
12+
name: 'prompt',
13+
description: 'This is a prompt command.',
14+
};
15+
16+
const handleSubmit: OnModalKitSubmit = async (interaction) => {
17+
const name = interaction.fields.getTextInputValue('name');
18+
const description = interaction.fields.getTextInputValue('description');
19+
20+
await interaction.reply({
21+
content: `Name: ${name}\nDescription: ${description}`,
22+
flags: MessageFlags.Ephemeral,
23+
});
24+
};
25+
26+
export async function run({ interaction }: SlashCommandProps) {
27+
const modal = (
28+
<Modal title="My Cool Modal" onSubmit={handleSubmit}>
29+
<ShortInput customId="name" label="Name" placeholder="John" required />
30+
<ParagraphInput
31+
customId="description"
32+
label="Description"
33+
placeholder="This is a description."
34+
/>
35+
</Modal>
36+
);
37+
38+
await interaction.showModal(modal);
39+
}

packages/commandkit/src/components/button/Button.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
type CommandKitButtonBuilderInteractionCollectorDispatchContextData,
77
} from './ButtonKit';
88
import { CommandKitElement } from '../common/element';
9+
import { MaybeArray } from '../common/types';
910

1011
export type ButtonChildrenLike = string | number | boolean;
1112

@@ -20,7 +21,7 @@ export interface ButtonProps {
2021
onClick?: CommandKitButtonBuilderInteractionCollectorDispatch;
2122
options?: CommandKitButtonBuilderInteractionCollectorDispatchContextData;
2223
onEnd?: CommandKitButtonBuilderOnEnd;
23-
children?: ButtonChildrenLike[] | ButtonChildrenLike;
24+
children?: MaybeArray<ButtonChildrenLike>;
2425
}
2526

2627
/**
@@ -36,13 +37,17 @@ export function Button(props: ButtonProps): CommandKitElement<'button-kit'> {
3637

3738
// auto-generate customId if not provided (only if onClick is set)
3839
if (props.onClick) {
39-
props.customId ??= crypto.randomUUID();
40+
props.customId ??= `buttonkit::${crypto.randomUUID()}`;
4041
}
4142

4243
if (props.customId) {
4344
button.setCustomId(props.customId);
4445
}
4546

47+
if (props.onClick) {
48+
button.onClick(props.onClick, props.options);
49+
}
50+
4651
if (props.disabled) {
4752
button.setDisabled(props.disabled);
4853
}
@@ -75,10 +80,6 @@ export function Button(props: ButtonProps): CommandKitElement<'button-kit'> {
7580
);
7681
}
7782

78-
if (props.onClick) {
79-
button.onClick(props.onClick, props.options);
80-
}
81-
8283
if (props.onEnd) {
8384
button.onEnd(props.onEnd);
8485
}

packages/commandkit/src/components/button/ButtonKit.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ export class ButtonKit extends ButtonBuilder {
149149
return this;
150150
}
151151

152+
private get customId() {
153+
// @ts-ignore
154+
return this.data.custom_id ?? this.data.customId;
155+
}
156+
152157
#setupInteractionCollector() {
153158
if (
154159
this.data.style === ButtonStyle.Link ||
@@ -157,12 +162,7 @@ export class ButtonKit extends ButtonBuilder {
157162
)
158163
return;
159164

160-
const myCustomId =
161-
'custom_id' in this.data
162-
? this.data.custom_id
163-
: 'customId' in this.data
164-
? this.data.customId
165-
: null;
165+
const myCustomId = this.customId ?? null;
166166

167167
if (myCustomId === null) {
168168
throw new TypeError(
@@ -177,12 +177,7 @@ export class ButtonKit extends ButtonBuilder {
177177
async (interaction) => {
178178
if (!interaction.isButton()) return;
179179

180-
const myCustomId =
181-
'custom_id' in this.data
182-
? this.data.custom_id
183-
: 'customId' in this.data
184-
? this.data.customId
185-
: null;
180+
const myCustomId = this.customId ?? null;
186181
const interactionCustomId = interaction.customId;
187182

188183
if (myCustomId && interactionCustomId !== myCustomId) return;

packages/commandkit/src/components/common/element.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
import { ActionRowBuilder } from 'discord.js';
1+
import { ActionRowBuilder, TextInputBuilder } from 'discord.js';
22
import type { ButtonKit } from '../button/ButtonKit';
33
import { warnUnstable } from '../../utils/warn-unstable';
4+
import { ModalKit } from '../modal/ModalKit';
45

56
export const ElementType = {
67
ActionRow: 'action-row',
78
Button: 'button-kit',
9+
Modal: 'modal',
10+
TextInput: 'text-input',
811
} as const;
912

1013
export type ElementType = (typeof ElementType)[keyof typeof ElementType];
1114

1215
export interface CommandKitElementData {
1316
[ElementType.ActionRow]: ActionRowBuilder;
1417
[ElementType.Button]: ButtonKit;
18+
[ElementType.Modal]: ModalKit;
19+
[ElementType.TextInput]: TextInputBuilder;
1520
}
1621

1722
export type CommandKitElement<Type extends ElementType> =
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type MaybeArray<T> = T | T[];

packages/commandkit/src/components/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,11 @@ export * from './action-row/ActionRow';
55
export * from './button/ButtonKit';
66
export * from './button/Button';
77

8+
// modals
9+
export * from './modal/ModalKit';
10+
export * from './modal/Modal';
11+
812
// common
913
export * from './common/element';
14+
export * from './common/types';
15+
export * from './common/EventInterceptor';
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { ActionRowBuilder, TextInputBuilder, TextInputStyle } from 'discord.js';
2+
import { MaybeArray } from '../common/types';
3+
import { CommandKitElement } from '../common/element';
4+
import {
5+
CommandKitModalBuilderInteractionCollectorDispatchContextData,
6+
ModalKit,
7+
OnModalKitEnd,
8+
OnModalKitSubmit,
9+
} from './ModalKit';
10+
11+
export interface ModalProps {
12+
customId?: string;
13+
title: string;
14+
children?: MaybeArray<TextInputBuilder | ActionRowBuilder>;
15+
onSubmit?: OnModalKitSubmit;
16+
onEnd?: OnModalKitEnd;
17+
options?: CommandKitModalBuilderInteractionCollectorDispatchContextData;
18+
}
19+
20+
/**
21+
* The modal component.
22+
* @param props The modal properties.
23+
* @returns The commandkit element.
24+
* @example <Modal title="My Modal" onSubmit={onSubmit}>...</Modal>
25+
*/
26+
export function Modal(props: ModalProps): CommandKitElement<'modal'> {
27+
const modal = new ModalKit();
28+
29+
if (props.title) {
30+
modal.setTitle(props.title);
31+
}
32+
33+
if (props.onSubmit) {
34+
props.customId ??= `modalkit::${crypto.randomUUID()}`;
35+
}
36+
37+
if (props.customId) {
38+
modal.setCustomId(props.customId);
39+
}
40+
41+
if (props.onSubmit) {
42+
modal.onSubmit(props.onSubmit, props.options);
43+
}
44+
45+
if (props.children) {
46+
const childs = (
47+
Array.isArray(props.children) ? props.children : [props.children]
48+
)
49+
.map((c) => {
50+
if (c instanceof ActionRowBuilder) return c;
51+
if (c instanceof TextInputBuilder)
52+
return new ActionRowBuilder().addComponents(c);
53+
})
54+
.filter((c): c is ActionRowBuilder<TextInputBuilder> => c != null);
55+
56+
modal.addComponents(childs);
57+
}
58+
59+
if (props.onEnd) {
60+
modal.onEnd(props.onEnd);
61+
}
62+
63+
return modal;
64+
}
65+
66+
export interface TextInputProps {
67+
customId: string;
68+
label: string;
69+
placeholder?: string;
70+
maxLength?: number;
71+
minLength?: number;
72+
value?: string;
73+
required?: boolean;
74+
}
75+
76+
/**
77+
* The text input component.
78+
* @param props The text input properties.
79+
* @returns The commandkit element.
80+
* @example <TextInput customId="input" label="Input" style={TextInputStyle.Short} />
81+
*/
82+
export function TextInput(
83+
props: TextInputProps & { style: TextInputStyle },
84+
): CommandKitElement<'text-input'> {
85+
const input = new TextInputBuilder().setStyle(props.style);
86+
87+
if (props.customId) {
88+
input.setCustomId(props.customId);
89+
}
90+
91+
if (props.label) {
92+
input.setLabel(props.label);
93+
}
94+
95+
if (props.placeholder) {
96+
input.setPlaceholder(props.placeholder);
97+
}
98+
99+
if (props.maxLength) {
100+
input.setMaxLength(props.maxLength);
101+
}
102+
103+
if (props.minLength) {
104+
input.setMinLength(props.minLength);
105+
}
106+
107+
if (props.value) {
108+
input.setValue(props.value);
109+
}
110+
111+
if (props.required) {
112+
input.setRequired(props.required);
113+
}
114+
115+
return input;
116+
}
117+
118+
/**
119+
* The short text input component.
120+
* @param props The text input properties.
121+
* @returns The commandkit element.
122+
* @example <ShortInput customId="input" label="Input" />
123+
*/
124+
export function ShortInput(
125+
props: TextInputProps,
126+
): CommandKitElement<'text-input'> {
127+
return TextInput({ ...props, style: TextInputStyle.Short });
128+
}
129+
130+
/**
131+
* The paragraph text input component.
132+
* @param props The text input properties.
133+
* @returns The commandkit element.
134+
* @example <ParagraphInput customId="input" label="Input" />
135+
*/
136+
export function ParagraphInput(
137+
props: TextInputProps,
138+
): CommandKitElement<'text-input'> {
139+
return TextInput({ ...props, style: TextInputStyle.Paragraph });
140+
}

0 commit comments

Comments
 (0)