Skip to content

Commit 11d0f2f

Browse files
committed
feat: components v2
1 parent f03eb57 commit 11d0f2f

28 files changed

+1438
-465
lines changed

src/builders/ActionRow.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import {
77
} from '../types';
88
import { BaseComponentBuilder } from './Base';
99
import { fromComponent } from './index';
10-
import type { BuilderComponents, FixedComponents } from './types';
10+
import type { ActionBuilderComponents, FixedComponents } from './types';
1111

1212
/**
1313
* Represents an Action Row component in a message.
1414
* @template T - The type of components in the Action Row.
1515
*/
16-
export class ActionRow<T extends BuilderComponents> extends BaseComponentBuilder<
16+
export class ActionRow<T extends ActionBuilderComponents = ActionBuilderComponents> extends BaseComponentBuilder<
1717
APIActionRowComponent<APIActionRowComponentTypes>
1818
> {
1919
/**
@@ -50,8 +50,8 @@ export class ActionRow<T extends BuilderComponents> extends BaseComponentBuilder
5050
* @example
5151
* actionRow.setComponents([buttonComponent1, buttonComponent2]);
5252
*/
53-
setComponents(component: FixedComponents<T>[]): this {
54-
this.components = [...component];
53+
setComponents(...component: RestOrArray<FixedComponents<T>>): this {
54+
this.components = component.flat() as FixedComponents<T>[];
5555
return this;
5656
}
5757

src/builders/Button.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import { type EmojiResolvable, resolvePartialEmoji } from '../common';
22
import { type APIButtonComponent, type APIMessageComponentEmoji, type ButtonStyle, ComponentType } from '../types';
3+
import { BaseComponentBuilder } from './Base';
34

45
/**
56
* Represents a button component.
67
* @template Type - The type of the button component.
78
*/
8-
export class Button {
9-
/**
10-
* Creates a new Button instance.
11-
* @param data - The initial data for the button.
12-
*/
13-
constructor(public data: Partial<APIButtonComponent> = {}) {
14-
this.data.type = ComponentType.Button;
9+
export class Button extends BaseComponentBuilder<APIButtonComponent> {
10+
constructor(data: Partial<APIButtonComponent> = {}) {
11+
super({ type: ComponentType.Button, ...data });
1512
}
1613

1714
/**
@@ -76,12 +73,4 @@ export class Button {
7673
(this.data as Extract<APIButtonComponent, { sku_id?: string }>).sku_id = skuId;
7774
return this;
7875
}
79-
80-
/**
81-
* Converts the Button instance to its JSON representation.
82-
* @returns The JSON representation of the Button instance.
83-
*/
84-
toJSON() {
85-
return { ...this.data } as Partial<APIButtonComponent>;
86-
}
8776
}

src/builders/Container.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { type ActionRow, fromComponent } from '.';
2+
import { type ColorResolvable, type RestOrArray, resolveColor } from '../common';
3+
import { type APIContainerComponent, ComponentType } from '../types';
4+
import { BaseComponentBuilder } from './Base';
5+
import type { File } from './File';
6+
import type { MediaGallery } from './MediaGallery';
7+
import type { Section } from './Section';
8+
import type { Separator } from './Separator';
9+
import type { TextDisplay } from './TextDisplay';
10+
11+
/**
12+
* Represents the possible component types that can be added to a Container.
13+
*/
14+
export type ContainerBuilderComponents = ActionRow | TextDisplay | Section | MediaGallery | Separator | File;
15+
16+
/**
17+
* Represents a container component builder.
18+
* Containers group other components together.
19+
* @example
20+
* ```ts
21+
* const container = new Container()
22+
* .addComponents(
23+
* new TextDisplay('This is text inside a container!'),
24+
* new ActionRow().addComponents(new Button().setLabel('Click me!'))
25+
* )
26+
* .setColor('Blue');
27+
* ```
28+
*/
29+
export class Container extends BaseComponentBuilder<APIContainerComponent> {
30+
/**
31+
* The components held within this container.
32+
*/
33+
components: ContainerBuilderComponents[];
34+
35+
/**
36+
* Constructs a new Container.
37+
* @param data Optional initial data for the container.
38+
*/
39+
constructor({ components, ...data }: Partial<APIContainerComponent> = {}) {
40+
super({ ...data, type: ComponentType.Container });
41+
this.components = (components?.map(fromComponent) ?? []) as ContainerBuilderComponents[];
42+
}
43+
44+
/**
45+
* Adds components to the container.
46+
* @param components The components to add. Can be a single component, an array of components, or multiple components as arguments.
47+
* @returns The updated Container instance.
48+
*/
49+
addComponents(...components: RestOrArray<ContainerBuilderComponents>) {
50+
this.components = this.components.concat(components.flat());
51+
return this;
52+
}
53+
54+
/**
55+
* Sets the components for the container, replacing any existing components.
56+
* @param components The components to set. Can be a single component, an array of components, or multiple components as arguments.
57+
* @returns The updated Container instance.
58+
*/
59+
setComponents(...components: RestOrArray<ContainerBuilderComponents>) {
60+
this.components = components.flat();
61+
return this;
62+
}
63+
64+
/**
65+
* Sets whether the container's content should be visually marked as a spoiler.
66+
* @param spoiler Whether the content is a spoiler (defaults to true).
67+
* @returns The updated Container instance.
68+
*/
69+
setSpoiler(spoiler = true) {
70+
this.data.spoiler = spoiler;
71+
return this;
72+
}
73+
74+
/**
75+
* Sets the accent color for the container.
76+
* @param color The color resolvable (e.g., hex code, color name, integer).
77+
* @returns The updated Container instance.
78+
*/
79+
setColor(color: ColorResolvable) {
80+
this.data.accent_color = resolveColor(color);
81+
return this;
82+
}
83+
84+
/**
85+
* Sets the ID for the container.
86+
* @param id The ID to set.
87+
* @returns The updated Container instance.
88+
*/
89+
setId(id: number) {
90+
this.data.id = id;
91+
return this;
92+
}
93+
94+
toJSON() {
95+
return {
96+
...this.data,
97+
components: this.components.map(c => c.toJSON()),
98+
} as APIContainerComponent;
99+
}
100+
}

src/builders/File.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { type APIFileComponent, ComponentType } from '../types';
2+
import { BaseComponentBuilder } from './Base';
3+
4+
/**
5+
* Represents a file component builder.
6+
* Used to display files within containers.
7+
* @example
8+
* ```ts
9+
* const file = new File()
10+
* .setMedia('https://example.com/image.png')
11+
* .setSpoiler();
12+
* ```
13+
*/
14+
export class File extends BaseComponentBuilder<APIFileComponent> {
15+
/**
16+
* Constructs a new File component.
17+
* @param data Optional initial data for the file component.
18+
*/
19+
constructor(data: Partial<APIFileComponent> = {}) {
20+
super({ type: ComponentType.File, ...data });
21+
}
22+
23+
/**
24+
* Sets the ID for the file component.
25+
* @param id The ID to set.
26+
* @returns The updated File instance.
27+
*/
28+
setId(id: number) {
29+
this.data.id = id;
30+
return this;
31+
}
32+
33+
/**
34+
* Sets the media URL for the file.
35+
* @param url The URL of the file to display.
36+
* @returns The updated File instance.
37+
*/
38+
setMedia(url: string) {
39+
this.data.file = { url };
40+
return this;
41+
}
42+
43+
/**
44+
* Sets whether the file should be visually marked as a spoiler.
45+
* @param spoiler Whether the file is a spoiler (defaults to true).
46+
* @returns The updated File instance.
47+
*/
48+
setSpoiler(spoiler = true) {
49+
this.data.spoiler = spoiler;
50+
return this;
51+
}
52+
}

src/builders/MediaGallery.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import type { RestOrArray } from '../common';
2+
import { type APIMediaGalleryComponent, type APIMediaGalleryItems, ComponentType } from '../types';
3+
import { BaseComponentBuilder } from './Base';
4+
5+
/**
6+
* Represents a media gallery component builder.
7+
* Used to display a collection of media items.
8+
* @example
9+
* ```ts
10+
* const gallery = new MediaGallery()
11+
* .addItems(
12+
* new MediaGalleryItem().setMedia('https://example.com/image1.png').setDescription('Image 1'),
13+
* new MediaGalleryItem().setMedia('https://example.com/image2.jpg').setSpoiler()
14+
* );
15+
* ```
16+
*/
17+
export class MediaGallery extends BaseComponentBuilder<APIMediaGalleryComponent> {
18+
items: MediaGalleryItem[];
19+
/**
20+
* Constructs a new MediaGallery.
21+
* @param data Optional initial data for the media gallery.
22+
*/
23+
constructor({ items, ...data }: Partial<APIMediaGalleryComponent> = {}) {
24+
super({ type: ComponentType.MediaGallery, ...data });
25+
this.items = (items?.map(i => new MediaGalleryItem(i)) ?? []) as MediaGalleryItem[];
26+
}
27+
/**
28+
* Sets the ID for the media gallery component.
29+
* @param id The ID to set.
30+
* @returns The updated MediaGallery instance.
31+
*/
32+
setId(id: number) {
33+
this.data.id = id;
34+
return this;
35+
}
36+
37+
/**
38+
* Adds items to the media gallery.
39+
* @param items The items to add. Can be a single item, an array of items, or multiple items as arguments.
40+
* @returns The updated MediaGallery instance.
41+
*/
42+
addItems(...items: RestOrArray<MediaGalleryItem>) {
43+
this.items = this.items.concat(items.flat());
44+
return this;
45+
}
46+
47+
/**
48+
* Sets the items for the media gallery, replacing any existing items.
49+
* @param items The items to set. Can be a single item, an array of items, or multiple items as arguments.
50+
* @returns The updated MediaGallery instance.
51+
*/
52+
setItems(...items: RestOrArray<MediaGalleryItem>) {
53+
this.items = items.flat();
54+
return this;
55+
}
56+
57+
toJSON() {
58+
return {
59+
...this.data,
60+
items: this.items.map(i => i.toJSON()),
61+
} as APIMediaGalleryComponent;
62+
}
63+
}
64+
65+
/**
66+
* Represents an item within a MediaGallery.
67+
*/
68+
export class MediaGalleryItem {
69+
/**
70+
* Constructs a new MediaGalleryItem.
71+
* @param data Optional initial data for the media gallery item.
72+
*/
73+
constructor(public data: Partial<APIMediaGalleryItems>) {}
74+
75+
/**
76+
* Sets the media URL for this gallery item.
77+
* @param url The URL of the media.
78+
* @returns The updated MediaGalleryItem instance.
79+
*/
80+
setMedia(url: string) {
81+
this.data.media = { url };
82+
return this;
83+
}
84+
85+
/**
86+
* Sets the description for this gallery item.
87+
* @param desc The description text.
88+
* @returns The updated MediaGalleryItem instance.
89+
*/
90+
setDescription(desc: string) {
91+
this.data.description = desc;
92+
return this;
93+
}
94+
95+
/**
96+
* Sets whether this gallery item should be visually marked as a spoiler.
97+
* @param spoiler Whether the item is a spoiler (defaults to true).
98+
* @returns The updated MediaGalleryItem instance.
99+
*/
100+
setSpoiler(spoiler = true) {
101+
this.data.spoiler = spoiler;
102+
return this;
103+
}
104+
105+
/**
106+
* Converts this MediaGalleryItem instance to its JSON representation.
107+
* @returns The JSON representation of the item data.
108+
*/
109+
toJSON() {
110+
return { ...this.data };
111+
}
112+
}

src/builders/Section.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { type Button, fromComponent } from '.';
2+
import type { RestOrArray } from '../common';
3+
import { type APISectionComponent, ComponentType } from '../types';
4+
import { BaseComponentBuilder } from './Base';
5+
import type { TextDisplay } from './TextDisplay';
6+
import type { Thumbnail } from './Thumbnail';
7+
8+
export class Section<
9+
Ac extends Button | Thumbnail = Button | Thumbnail,
10+
> extends BaseComponentBuilder<APISectionComponent> {
11+
components: TextDisplay[];
12+
accessory!: Ac;
13+
constructor({ components, accessory, ...data }: Partial<APISectionComponent> = {}) {
14+
super({ type: ComponentType.Section, ...data });
15+
this.components = (components?.map(component => fromComponent(component)) ?? []) as TextDisplay[];
16+
if (accessory) this.accessory = fromComponent(accessory) as Ac;
17+
}
18+
19+
/**
20+
* Adds components to this section.
21+
* @param components The components to add
22+
* @example section.addComponents(new TextDisplay().content('Hello'));
23+
*/
24+
addComponents(...components: RestOrArray<TextDisplay>) {
25+
this.components = this.components.concat(components.flat());
26+
return this;
27+
}
28+
29+
/**
30+
* Sets the components for this section.
31+
* @param components The components to set
32+
* @example section.setComponents(new TextDisplay().content('Hello'));
33+
*/
34+
setComponents(...components: RestOrArray<TextDisplay>) {
35+
this.components = components.flat();
36+
return this;
37+
}
38+
39+
setAccessory(accessory: Ac) {
40+
this.accessory = accessory;
41+
return this;
42+
}
43+
44+
/**
45+
* Converts this section to JSON.
46+
* @returns The JSON representation of this section
47+
*/
48+
toJSON() {
49+
return {
50+
...this.data,
51+
components: this.components.map(component => component.toJSON()),
52+
accessory: this.accessory.toJSON(),
53+
} as APISectionComponent;
54+
}
55+
}

0 commit comments

Comments
 (0)