+ * @package core
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../../../../config.php');
+
+defined('BEHAT_SITE_RUNNING') || die();
+
+global $CFG, $PAGE, $OUTPUT;
+$PAGE->set_url('/lib/tests/behat/fixtures/dropdown_dialog_testpage.php');
+$PAGE->add_body_class('limitedwidth');
+require_login();
+$PAGE->set_context(core\context\system::instance());
+
+echo $OUTPUT->header();
+
+echo "Dropdown dialog test page
";
+
+echo '';
+echo "
Basic example
";
+$dialog = new core\output\local\dropdown\dialog('Open dialog', 'Dialog content');
+echo $OUTPUT->render($dialog);
+echo '';
+
+echo '';
+echo "
Rich content example
";
+$content = '
+
Some rich content element.
+
+';
+$dialog = new core\output\local\dropdown\dialog('Open dialog', $content);
+echo $OUTPUT->render($dialog);
+echo '
';
+
+echo '';
+echo "
Rich button example
";
+$button = $OUTPUT->pix_icon('t/hide', 'Eye icon') . ' Click to open';
+$dialog = new core\output\local\dropdown\dialog($button, 'Dialog content');
+echo $OUTPUT->render($dialog);
+echo '';
+
+echo '';
+echo "
CSS override example
";
+$dialog = new core\output\local\dropdown\dialog(
+ 'Open dialog',
+ 'Dialog content',
+ [
+ 'buttonclasses' => 'btn btn-primary extraclass',
+ ]
+);
+echo $OUTPUT->render($dialog);
+echo '';
+
+echo '';
+
+echo '';
+echo "
Custom element id
";
+$dialog = new core\output\local\dropdown\dialog('Open dialog', 'Dialog content', [
+ 'extras' => ['id' => 'CustomDropdownButtonId'],
+]);
+echo $OUTPUT->render($dialog);
+echo '';
+
+$inlinejs = "document.querySelector('#CustomDropdownButtonId button').innerHTML = 'Custom ID button found';";
+$PAGE->requires->js_amd_inline($inlinejs);
+
+echo '';
+echo "
Dropdown position example
";
+$dialog = new core\output\local\dropdown\dialog('Open dialog', 'Dialog content');
+$dialog->set_position(core\output\local\dropdown\dialog::POSITION['end']);
+echo $OUTPUT->render($dialog);
+echo '';
+
+echo '';
+echo "
Dropdown max width values example
";
+$content = '
+ Some long long content. Some long long content. Some long long content. Some long long content.
+ Some long long content. Some long long content. Some long long content. Some long long content.
+ Some long long content. Some long long content. Some long long content. Some long long content.
+';
+
+$dialog = new core\output\local\dropdown\dialog('Default dialog (adaptative)', $content);
+$dialog->set_classes('mb-3');
+echo $OUTPUT->render($dialog);
+
+$dialog = new core\output\local\dropdown\dialog('Big dialog', $content);
+$dialog->set_dialog_width(core\output\local\dropdown\dialog::WIDTH['big']);
+$dialog->set_classes('mb-3');
+echo $OUTPUT->render($dialog);
+
+$dialog = new core\output\local\dropdown\dialog('Small dialog', $content);
+$dialog->set_dialog_width(core\output\local\dropdown\dialog::WIDTH['small']);
+$dialog->set_classes('mb-3');
+echo $OUTPUT->render($dialog);
+echo '';
+
+echo "Dropdown status test page
";
+
+echo '';
+echo "
Basic example
";
+$choice = new core\output\choicelist('Dialog content');
+$choice->add_option('option1', 'Option 1', [
+ 'description' => 'Option 1 description'
+]);
+$choice->add_option('option2', 'Option 2', [
+ 'description' => 'Option 2 description',
+ 'icon' => new pix_icon('t/hide', 'Eye icon 1')
+]);
+$choice->add_option('option3', 'Option 3', [
+ 'icon' => new pix_icon('t/show', 'Eye icon 2')
+]);
+$dialog = new core\output\local\dropdown\status('Open dialog', $choice);
+echo $OUTPUT->render($dialog);
+echo '';
+
+echo '';
+echo "
Selected element example
";
+$choice = new core\output\choicelist('Dialog content');
+$choice->add_option('option1', 'Option 1', [
+ 'description' => 'Option 1 description',
+ 'icon' => new pix_icon('t/show', 'Eye icon 1')
+]);
+$choice->add_option('option2', 'Option 2', [
+ 'description' => 'Option 2 description',
+ 'icon' => new pix_icon('t/hide', 'Eye icon 2')
+]);
+$choice->add_option('option3', 'Option 3', [
+ 'description' => 'Option 3 description',
+ 'icon' => new pix_icon('t/stealth', 'Eye icon 3')
+]);
+$choice->set_selected_value('option2');
+$dialog = new core\output\local\dropdown\status('Open dialog', $choice);
+echo $OUTPUT->render($dialog);
+echo '';
+
+echo '';
+echo "
Disable option example
";
+$choice = new core\output\choicelist('Dialog content');
+$choice->add_option('option1', 'Option 1');
+$choice->add_option('option2', 'Option 2', [
+ 'description' => 'Option 2 description',
+ 'icon' => new pix_icon('t/hide', 'Eye icon')
+]);
+$choice->add_option('option3', 'Option 3');
+$choice->set_option_disabled('option2', true);
+$dialog = new core\output\local\dropdown\status('Open dialog', $choice);
+echo $OUTPUT->render($dialog);
+echo '';
+
+echo '';
+
+echo '';
+echo "
Set option url example
";
+$choice = new core\output\choicelist('Dialog content');
+$choice->add_option('option1', 'Option 1');
+$choice->add_option('option2', 'Option 2', [
+ 'url' => new moodle_url('/lib/tests/behat/fixtures/dropdown_output_testpage.php', ['foo' => 'bar']),
+]);
+$choice->add_option('option3', 'Option 3');
+$choice->set_option_extras('option2', ['data-foo' => 'bar']);
+$dialog = new core\output\local\dropdown\status('Open dialog', $choice);
+echo $OUTPUT->render($dialog);
+$foo = optional_param('foo', 'none', PARAM_TEXT);
+echo "
Foo param value: $foo
";
+echo '
';
+
+echo $OUTPUT->footer();
diff --git a/lib/tests/output/choicelist_test.php b/lib/tests/output/choicelist_test.php
new file mode 100644
index 00000000000..e913a666327
--- /dev/null
+++ b/lib/tests/output/choicelist_test.php
@@ -0,0 +1,195 @@
+.
+
+/**
+ * Unit tests for core\output\choice class.
+ *
+ * @package core
+ * @category test
+ * @copyright 2023 Ferran Recio
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\output;
+
+use advanced_testcase;
+
+/**
+ * Unit tests for the `icon_system` class.
+ *
+ * @coversDefaultClass \core\output\choicelist
+ */
+class choicelist_test extends advanced_testcase {
+ /**
+ * Test for a choice without options.
+ *
+ * @covers ::_construct
+ * @covers ::add_option
+ * @covers ::export_for_template
+ */
+ public function test_empty_export(): void {
+ $page = new \moodle_page();
+ $page->set_url('/user/profile.php');
+ $page->set_context(\context_system::instance());
+ $renderer = $page->get_renderer('core');
+
+ $choice = new choicelist('Choose an option');
+ $export = $choice->export_for_template($renderer);
+
+ $this->assertEquals('Choose an option', $export['description']);
+ $this->assertEquals(false, $export['hasoptions']);
+ $this->assertEquals([], $export['options']);
+ }
+
+ /**
+ * Test for a choice with basic options.
+ *
+ * @covers ::_construct
+ * @covers ::add_option
+ * @covers ::export_for_template
+ */
+ public function test_basic_export(): void {
+ $page = new \moodle_page();
+ $page->set_url('/user/profile.php');
+ $page->set_context(\context_system::instance());
+ $renderer = $page->get_renderer('core');
+
+ $choice = new choicelist('Choose an option');
+ $choice->add_option('option1', 'Option 1');
+ $choice->add_option('option2', 'Option 2');
+
+ $export = $choice->export_for_template($renderer);
+
+ $this->assertEquals('Choose an option', $export['description']);
+ $this->assertEquals(true, $export['hasoptions']);
+ $this->assertCount(2, $export['options']);
+ $this->validate_option($export['options'][0], 'option1', 'Option 1', []);
+ $this->validate_option($export['options'][1], 'option2', 'Option 2', []);
+ }
+ /**
+ * Test for a choice with extras options definition.
+ *
+ * @covers ::_construct
+ * @covers ::add_option
+ * @covers ::set_option_extras
+ * @covers ::export_for_template
+ */
+ public function test_option_defintion_export(): void {
+ $page = new \moodle_page();
+ $page->set_url('/user/profile.php');
+ $page->set_context(\context_system::instance());
+ $renderer = $page->get_renderer('core');
+
+ $choice = new choicelist('Choose an option');
+ $definition1 = [
+ 'disabled' => true,
+ 'description' => 'Description',
+ 'url' => new \moodle_url('/user/profile.php'),
+ 'icon' => new \pix_icon('i/grade', 'Grade'),
+ 'extras' => [
+ 'data-attribute' => 'value',
+ ],
+ ];
+ $choice->add_option('option1', 'Option 1', $definition1);
+ $definition2 = [
+ 'disabled' => false,
+ 'description' => null,
+ 'url' => null,
+ 'icon' => null,
+ 'extras' => null,
+ ];
+ $choice->add_option('option2', 'Option 2', $definition2);
+
+ $export = $choice->export_for_template($renderer);
+
+ $this->assertEquals('Choose an option', $export['description']);
+ $this->assertEquals(true, $export['hasoptions']);
+ $this->assertCount(2, $export['options']);
+ $definition1['iconexport'] = $definition1['icon']->export_for_template($renderer);
+ $this->validate_option($export['options'][0], 'option1', 'Option 1', $definition1);
+ $this->validate_option($export['options'][1], 'option2', 'Option 2', $definition2);
+ }
+
+ /**
+ * Test for a choice with option selected.
+ *
+ * @covers ::_construct
+ * @covers ::add_option
+ * @covers ::set_selected_value
+ * @covers ::get_selected_value
+ * @covers ::export_for_template
+ */
+ public function test_option_selected_export(): void {
+ $page = new \moodle_page();
+ $page->set_url('/user/profile.php');
+ $page->set_context(\context_system::instance());
+ $renderer = $page->get_renderer('core');
+
+ $choice = new choicelist('Choose an option');
+ $choice->add_option('option1', 'Option 1');
+ $choice->add_option('option2', 'Option 2');
+ $choice->set_selected_value('option1');
+
+ $export = $choice->export_for_template($renderer);
+
+ $this->assertEquals('Choose an option', $export['description']);
+ $this->assertEquals(true, $export['hasoptions']);
+ $this->assertCount(2, $export['options']);
+ $this->assertEquals('option1', $choice->get_selected_value());
+ $this->validate_option($export['options'][0], 'option1', 'Option 1', [], true);
+ $this->validate_option($export['options'][1], 'option2', 'Option 2', []);
+ }
+
+ /**
+ * Validate a choice option export.
+ * @param array $option the option export
+ * @param string $value the option value
+ * @param string $name the option name
+ * @param array|null $definition the option definition
+ * @param bool $selected if the option is selected
+ */
+ private function validate_option(
+ array $option,
+ string $value,
+ string $name,
+ ?array $definition,
+ bool $selected = false
+ ): void {
+ $this->assertEquals($value, $option['value']);
+ $this->assertEquals($name, $option['name']);
+ $this->assertEquals($definition['disabled'] ?? false, $option['disabled']);
+ $this->assertEquals($definition['description'] ?? null, $option['description']);
+ if (isset($definition['url'])) {
+ $this->assertEquals($definition['url']->out(true), $option['url']);
+ $this->assertTrue($option['hasurl']);
+ }
+ if (isset($definition['icon'])) {
+ $this->assertEquals($definition['iconexport'], $option['icon']);
+ $this->assertTrue($option['hasicon']);
+ }
+ if ($selected) {
+ $this->assertTrue($option['selected']);
+ }
+ if (isset($definition['extras'])) {
+ foreach ($option['extras'] as $extra) {
+ $attribute = $extra['attribute'];
+ $this->assertEquals($definition['extras'][$attribute], $extra['value']);
+ }
+ } else {
+ $this->assertFalse(isset($option['extras']));
+ }
+ }
+}
diff --git a/theme/boost/scss/moodle/core.scss b/theme/boost/scss/moodle/core.scss
index 114b347eb88..dd3cf2a9afd 100644
--- a/theme/boost/scss/moodle/core.scss
+++ b/theme/boost/scss/moodle/core.scss
@@ -2624,6 +2624,25 @@ input[disabled] {
visibility: hidden;
}
+// Any dialog (modal or dropdown) forced max-widths limits.
+.dialog-big {
+ max-width: $modal-md;
+}
+
+.dialog-small {
+ max-width: $modal-sm;
+}
+
+@include media-breakpoint-up(sm) {
+ .dialog-big {
+ width: $modal-md;
+ }
+
+ .dialog-small {
+ width: $modal-sm;
+ }
+}
+
// Emoji picker.
$picker-width: 350px !default;
$picker-width-xs: 320px !default;
diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css
index 54c3f2c7a56..ce069538a5a 100644
--- a/theme/boost/style/moodle.css
+++ b/theme/boost/style/moodle.css
@@ -25432,6 +25432,22 @@ input[disabled] {
visibility: hidden;
}
+.dialog-big {
+ max-width: 500px;
+}
+
+.dialog-small {
+ max-width: 300px;
+}
+
+@media (min-width: 576px) {
+ .dialog-big {
+ width: 500px;
+ }
+ .dialog-small {
+ width: 300px;
+ }
+}
.emoji-picker {
width: 350px;
height: 400px;
diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css
index 2c8445d995a..89c87d338c9 100644
--- a/theme/classic/style/moodle.css
+++ b/theme/classic/style/moodle.css
@@ -25432,6 +25432,22 @@ input[disabled] {
visibility: hidden;
}
+.dialog-big {
+ max-width: 500px;
+}
+
+.dialog-small {
+ max-width: 300px;
+}
+
+@media (min-width: 576px) {
+ .dialog-big {
+ width: 500px;
+ }
+ .dialog-small {
+ width: 300px;
+ }
+}
.emoji-picker {
width: 350px;
height: 400px;