Skip to content

Label and Select Menu support for Modals #1377

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Discord/Builders/Components/ActionRow.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
*/
class ActionRow extends Layout
{
/** Usage of ActionRow in Modal is deprecated. Use `Component::Label` as the top-level container. */
public const USAGE = ['Message', 'Modal'];

/**
Expand Down
1 change: 1 addition & 0 deletions src/Discord/Builders/Components/Component.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ abstract class Component implements JsonSerializable
public const TYPE_SEPARATOR = 14;
public const TYPE_CONTENT_INVENTORY_ENTRY = 16; // Not documented
public const TYPE_CONTAINER = 17;
public const TYPE_LABEL = 18;

/** @deprecated 7.4.0 Use `Component::TYPE_STRING_SELECT` */
public const TYPE_SELECT_MENU = 3;
Expand Down
156 changes: 156 additions & 0 deletions src/Discord/Builders/Components/Label.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php

declare(strict_types=1);

/*
* This file is a part of the DiscordPHP project.
*
* Copyright (c) 2015-present David Cole <[email protected]>
*
* This file is subject to the MIT license that is bundled
* with this source code in the LICENSE.md file.
*/

namespace Discord\Builders\Components;

use function Discord\poly_strlen;

/**
* A Label is a top-level component.
*
* @link https://discord.com/developers/docs/components/reference#label
*
* @todo Update to match Discord's documentation upon public release.
* @todo Update Label class to extend the relevant base class.
* @todo Confirm if Label will be usable in Message components.
*
* @since 10.19.0
*
* @property int $type 18 for label component.
* @property string $label The text for the label. Must be between 1 and 45 characters.
* @property string|null $description Optional description for the label. Max 100 characters.
* @property StringSelect|TextInput $component The component associated with the label.
*/
class Label extends ComponentObject
{
public const USAGE = ['Modal'];

/**
* Component type.
*
* @var int
*/
protected $type = Component::TYPE_LABEL;

/**
* The text for the label.
*
* @var string
*/
protected $label;

/**
* Optional description for the label.
*
* @var string|null
*/
protected $description;

/**
* The component associated with the label.
*
* @var StringSelect|TextInput
*/
protected $component;

/**
* Creates a new label component.
*
* @param string $label The text for the label.
* @param StringSelect|TextInput $component The component associated with the label.
* @param string|null $description Optional description for the label.
*
* @return self
*/
public static function new(string $label, $component, ?string $description = null): self
{
$label_component = new self();

$label_component->setLabel($label);
$label_component->setComponent($component);
$label_component->setDescription($description);

return $label_component;
}

/**
* Sets the label text.
*
* @param string $label The text for the label. Must be between 1 and 45 characters.
*
* @return self
*/
public function setLabel(string $label): self
{
if (poly_strlen($label) === 0 || poly_strlen($label) > 45) {
throw new \LengthException('Label must be between 1 and 45 in length.');
}

$this->label = $label;

return $this;
}

/**
* Sets the description text.
*
* @param string|null $description The description for the label. Max 100 characters.
*
* @return self
*/
public function setDescription(?string $description = null): self
{
if (poly_strlen($description) === 0) {
$description = null;
}

if (poly_strlen($description) > 100) {
throw new \LengthException('Description must be between 0 and 100 in length.');
}

$this->description = $description;

return $this;
}

/** Sets the component associated with the label.
*
* @param StringSelect|TextInput $component The component associated with the label.
*
* @return self
*/
public function setComponent($component): self
{
$this->component = $component;

return $this;
}

/**
* @inheritDoc
*/
public function jsonSerialize(): array
{
$data = [
'type' => $this->type,
'label' => $this->label,
'component' => $this->component,
];

if (isset($this->description)) {
$data['description'] = $this->description;
}

return $data;
}
}
26 changes: 20 additions & 6 deletions src/Discord/Builders/Components/SelectMenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ abstract class SelectMenu extends Interactive
*/
protected $disabled;

/**
* Whether the select menu is required. Defaults to true. (Modal only).
*
* @var bool|null
*/
protected $required;

/**
* Callback used to listen for `INTERACTION_CREATE` events.
*
Expand Down Expand Up @@ -296,7 +303,7 @@ public function setMaxValues(?int $max_values): self
}

/**
* Sets the select menus disabled state.
* Sets the select menus disabled state. (Message only)
*
* @param bool $disabled
*
Expand Down Expand Up @@ -505,7 +512,7 @@ public function isDisabled(): ?bool
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function jsonSerialize(): array
{
Expand All @@ -532,24 +539,31 @@ public function jsonSerialize(): array

if (isset($this->min_values)) {
if (isset($this->options) && $this->min_values > count($this->options)) {
throw new \OutOfBoundsException('There are less options than the minimum number of options to be selected.');
throw new \DomainException('There are less options than the minimum number of options to be selected.');
}

$content['min_values'] = $this->min_values;
}

if ($this->max_values) {
if (isset($this->max_values) && $this->max_values) {
if (isset($this->options) && $this->max_values > count($this->options)) {
throw new \OutOfBoundsException('There are less options than the maximum number of options to be selected.');
throw new \DomainException('There are less options than the maximum number of options to be selected.');
}

$content['max_values'] = $this->max_values;
}

if ($this->disabled) {
if (isset($this->disabled) && $this->disabled) {
$content['disabled'] = true;
}

if (isset($this->required)) {
$content['required'] = true;
if ($this->min_values === null || $this->min_values === 0) {
throw new \LengthException('Required select menus must have a minimum value greater than 0.');
}
}

return $content;
}
}
16 changes: 15 additions & 1 deletion src/Discord/Builders/Components/StringSelect.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/
class StringSelect extends SelectMenu
{
public const USAGE = ['Message'];
public const USAGE = ['Message', 'Modal'];

/**
* Component type.
Expand All @@ -39,6 +39,20 @@ class StringSelect extends SelectMenu
*/
protected $options = [];

/**
* Sets whether the select menu is required. (Modal only).
*
* @param bool|null $required
*
* @return $this
*/
public function setRequired(?bool $required = false): self
{
$this->required = $required;

return $this;
}

/**
* Adds an option to the select menu. Maximum 25 options.
*
Expand Down
22 changes: 14 additions & 8 deletions src/Discord/Builders/Components/TextInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ class TextInput extends Interactive
/**
* Label for the text input.
*
* @var string
* Deprecated for use with modals. Use a top-level Component::Label.
*
* @var string|null
*
*/
private $label;

Expand Down Expand Up @@ -92,7 +95,7 @@ class TextInput extends Interactive
* @param int $style The style of the text input.
* @param string|null $custom_id The custom ID of the text input. If not given, a UUID will be used
*/
public function __construct(string $label, int $style, ?string $custom_id = null)
public function __construct(?string $label = null, int $style, ?string $custom_id = null)
{
$this->setLabel($label);
$this->setStyle($style);
Expand All @@ -108,7 +111,7 @@ public function __construct(string $label, int $style, ?string $custom_id = null
*
* @return self
*/
public static function new(string $label, int $style, ?string $custom_id = null): self
public static function new(?string $label = null, int $style, ?string $custom_id = null): self
{
return new self($label, $style, $custom_id);
}
Expand Down Expand Up @@ -156,15 +159,15 @@ public function setStyle(int $style): self
/**
* Sets the label of the text input.
*
* @param string $label Label of the text input. Maximum 45 characters.
* @param string|null $label Label of the text input. Maximum 45 characters.
*
* @throws \LengthException
*
* @return $this
*/
public function setLabel(string $label): self
public function setLabel(?string $label = null): self
{
if (poly_strlen($label) > 45) {
if (isset($label) && poly_strlen($label) > 45) {
throw new \LengthException('Label must be maximum 45 characters.');
}

Expand Down Expand Up @@ -318,17 +321,20 @@ public function isRequired(): ?bool
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function jsonSerialize(): array
{
$content = [
'type' => $this->type,
'custom_id' => $this->custom_id,
'style' => $this->style,
'label' => $this->label,
];

if (isset($this->label)) {
$content['label'] = $this->label;
}

if (isset($this->min_length)) {
$content['min_length'] = $this->min_length;
}
Expand Down
6 changes: 4 additions & 2 deletions src/Discord/Parts/Channel/Message/ActionRow.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
namespace Discord\Parts\Channel\Message;

/**
* An Action Row is a top-level layout component used in messages and modals.
* An Action Row is a top-level layout component used in messages.
*
* Using ActionRows in modals is now deprecated - use Component::Label as the top level component!
*
* Action Rows can contain:

*
* Up to 5 contextually grouped buttons
* A single text input
* A single select component (string select, user select, role select, mentionable select, or channel select)
Expand Down
3 changes: 2 additions & 1 deletion src/Discord/Parts/Channel/Message/Component.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ class Component extends Part
ComponentBuilder::TYPE_FILE => File::class,
ComponentBuilder::TYPE_SEPARATOR => Separator::class,
ComponentBuilder::TYPE_CONTAINER => Container::class,
ComponentBuilder::TYPE_LABEL => Label::class,
];

/**
* {@inheritDoc}
* @inheritDoc
*/
protected $fillable = [
'type',
Expand Down
Loading