Skip to content

Commit c36d2da

Browse files
committed
docs: write interactive components
1 parent e8f25e8 commit c36d2da

File tree

2 files changed

+247
-1
lines changed

2 files changed

+247
-1
lines changed

.astro/data-store.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/content/docs/api/interactive-components.mdx

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,250 @@ permalink: interactive-components
55
icon: lucide:mouse-pointer-2
66
order: 5
77
---
8+
89
# Interactive Components
10+
11+
Interactive components are a turnkey solution that allow you to drastically simplify your code and
12+
make your code easier to read and understand.
13+
14+
---
15+
16+
## Introduction
17+
18+
The platform provides a number of components that we have explained [here](/docs/api/components).
19+
There are three precise steps to using them:
20+
21+
1. Define the component
22+
2. Send it to Discord
23+
3. Reaction to its use by a user.
24+
25+
We can define the use of a component (in this case, a button) using the following example.
26+
27+
### Create our component
28+
29+
First of all, we're going to create our component so that we can use it later.
30+
This declaration can be made within a handler or anywhere else in your application.
31+
32+
```dart
33+
final button = ButtonBuilder.primary('customId',
34+
label: 'Click me',
35+
emoji: PartialEmoji.fromUnicode('👍'),
36+
disabled: true,
37+
);
38+
```
39+
40+
### Send to Discord
41+
42+
For our example, we'll take the case where we want to send our component when a user writes a message on a Discord server.
43+
44+
So we'll use the `MessageCreateEvent` event.
45+
46+
```dart
47+
void main(_, port) async {
48+
final client = ClientBuilder()
49+
.setHmrDevPort(port)
50+
.build();
51+
52+
client.events.server.messageCreate((ServerMessage message) async {
53+
if (!message.authorIsBot) {
54+
final channel = await message.resolveMember();
55+
56+
final builder = MessageComponentBuilder()
57+
..text('# Hello from World')
58+
// [!code ++]
59+
..button(button);
60+
61+
await channel.reply(builder);
62+
}
63+
});
64+
65+
await client.init();
66+
}
67+
```
68+
69+
### Reaction to its use by a user
70+
71+
Now that a user has clicked on our button, we want to be able to send them an ephemeral message.
72+
73+
```dart
74+
client.events.interaction.buttonClick((ButtonContext ctx) async {
75+
if (ctx case ServerButtonContext(:final interaction)) {
76+
final builder = MessageComponentBuilder()
77+
..text('# Thanks for clicking on the button');
78+
79+
await interaction.reply(builder);
80+
}
81+
});
82+
```
83+
84+
As we have seen, the use of simple components quickly becomes "heavy" to manage.
85+
86+
That's why we're proposing a new approach: `interactive components`.
87+
88+
---
89+
90+
## Create your own interactive components
91+
92+
Interactive components are a turnkey solution designed to considerably simplify all these processes through a more declarative approach.
93+
94+
A component is nothing more or less than a class comprising 3 main elements:
95+
96+
1. `customId`: the unique identifier of the component
97+
2. `build`: the method used to build the component
98+
3. `handler`: the method used to react to the use of the component
99+
100+
---
101+
102+
### Choosing the customId
103+
104+
First we will declare the `customId` property of our component.
105+
106+
> [!important]
107+
> This property must be unique for each component within the same application
108+
109+
```dart
110+
final class MyButton implements InteractiveButton {
111+
// [!code ++:2]
112+
@override
113+
String get customId => 'button::test';
114+
}
115+
```
116+
117+
---
118+
119+
### Component definition
120+
121+
We design the component to be sent to the platform.
122+
123+
```dart
124+
final class MyButton implements InteractiveButton {
125+
@override
126+
String get customId => 'button::test';
127+
128+
// [!code ++:7]
129+
@override
130+
ButtonBuilderContract build() {
131+
return ButtonBuilder.primary(customId,
132+
label: 'Click me',
133+
emoji: PartialEmoji.fromUnicode('👍'),
134+
disabled: true,
135+
);
136+
}
137+
}
138+
```
139+
140+
---
141+
142+
### Handling the component
143+
144+
We apply a behaviour to the action performed by a user
145+
146+
> [!note]
147+
> Within the handler, it's important to note that we don't know the context in which the button was clicked, so you'll have to check this manually
148+
149+
```dart
150+
final class MyButton implements InteractiveButton {
151+
@override
152+
String get customId => 'button::test';
153+
154+
// [!code ++:9]
155+
@override
156+
Future<void> handle(ButtonContext ctx) async {
157+
if (ctx case ServerButtonContext(:final interaction)) {
158+
final builder = MessageComponentBuilder()
159+
..text('# Hello from World');
160+
161+
await interaction.reply(builder);
162+
}
163+
}
164+
165+
@override
166+
ButtonBuilderContract build() {
167+
return ButtonBuilder.primary(customId,
168+
label: 'Click me',
169+
emoji: PartialEmoji.fromUnicode('👍'),
170+
disabled: true,
171+
);
172+
}
173+
}
174+
```
175+
176+
---
177+
178+
### Registering the component
179+
180+
Finally, we register our component in the client.
181+
182+
```dart
183+
void main() async {
184+
final client = ClientBuilder()
185+
.setHmrDevPort(port)
186+
.build();
187+
188+
// [!code ++]
189+
client.register(MyButton.new);
190+
191+
await client.init();
192+
}
193+
```
194+
195+
## Use your component
196+
197+
Using the example above, it would be usual to instantiate our interactive component and then call the `build()` method to retrieve the component builder.
198+
199+
This approach is indeed the right one, with one small exception: your component will have to be instantiated N times to be used N times.
200+
201+
To avoid this unnecessary re-instantiation, we provide a service that allows you to retrieve a single instance of your interactive component.
202+
203+
> [!note]
204+
> We assume that no two components can have the same `customId`.
205+
206+
In a relatively functional approach, we provide an access point to your registered components from the `client` instance.
207+
208+
```dart
209+
client.events.server.messageCreate((ServerMessage message) async {
210+
if (!message.authorIsBot) {
211+
final channel = await message.resolveChannel<ServerTextChannel>();
212+
213+
// [!code --]
214+
final button = MyButton();
215+
// [!code ++]
216+
final button = client.components.get('button::test');
217+
218+
final builder = MessageComponentBuilder()
219+
..text('# Hello from World')
220+
// [!code ++]
221+
..button(button.build());
222+
223+
await message.reply(builder);
224+
}
225+
});
226+
```
227+
228+
In the case of a class, we provide a `Component` mixin which allows you to access all the interactive components registered in your application.
229+
230+
```dart
231+
final class FooCommand with Component implements CommandDefinition {
232+
Future<void> handle(ServerCommandContext ctx) async {
233+
// [!code --]
234+
final button = MyButton();
235+
// [!code ++]
236+
final button = client.components.get('button::test');
237+
238+
final builder = MessageComponentBuilder()
239+
..text('# Hello from World')
240+
// [!code ++]
241+
..button(button.build());
242+
243+
await ctx.interaction.reply(builder);
244+
}
245+
246+
@override
247+
CommandDefinitionBuilder build() {
248+
return CommandDefinitionBuilder()
249+
..using(File('lib/commands.yaml'))
250+
..setHandler('handle', handle);
251+
}
252+
}
253+
254+
```

0 commit comments

Comments
 (0)