From beacf24a89f937db757cade801d545ce94360bc5 Mon Sep 17 00:00:00 2001
From: Elmar Ludwig <elmar.ludwig@uni-osnabrueck.de>
Date: Tue, 21 Nov 2023 15:16:53 +0000
Subject: [PATCH] add API for checkboxes/radio buttons in action menus, fixes
 #3482

Closes #3482

Merge request studip/studip!2388
---
 lib/classes/ActionMenu.php                    | 38 +++++++++++++++++++
 resources/vue/components/AdminCourses.vue     |  5 ++-
 resources/vue/components/StudipActionMenu.vue | 15 ++++++--
 3 files changed, 53 insertions(+), 5 deletions(-)

diff --git a/lib/classes/ActionMenu.php b/lib/classes/ActionMenu.php
index cf4fb80cd4f..929c485189e 100644
--- a/lib/classes/ActionMenu.php
+++ b/lib/classes/ActionMenu.php
@@ -207,6 +207,44 @@ class ActionMenu
         return $this;
     }
 
+    /**
+     * Adds a checkbox to the list of actions.
+     *
+     * @param String $name       Input name
+     * @param String $label      Label displayed in the menu
+     * @param bool   $checked    Checked state of the action
+     * @param array  $attributes Optional attributes to add to the button
+     * @return ActionMenu instance to allow chaining
+     */
+    public function addCheckbox($name, $label, bool $checked, array $attributes = [])
+    {
+        return $this->addButton(
+            $name,
+            $label,
+            Icon::create($checked ? 'checkbox-checked' : 'checkbox-unchecked'),
+            $attributes + ['role' => 'checkbox', 'aria-checked' => $checked ? 'true' : 'false']
+        );
+    }
+
+    /**
+     * Adds a radio button to the list of actions.
+     *
+     * @param String $name       Input name
+     * @param String $label      Label displayed in the menu
+     * @param bool   $checked    Checked state of the action
+     * @param array  $attributes Optional attributes to add to the button
+     * @return ActionMenu instance to allow chaining
+     */
+    public function addRadioButton($name, $label, bool $checked, array $attributes = [])
+    {
+        return $this->addButton(
+            $name,
+            $label,
+            Icon::create($checked ? 'radiobutton-checked' : 'radiobutton-unchecked'),
+            $attributes + ['role' => 'radio', 'aria-checked' => $checked ? 'true' : 'false']
+        );
+    }
+
     /**
      * Adds a MultiPersonSearch object to the list of actions.
      *
diff --git a/resources/vue/components/AdminCourses.vue b/resources/vue/components/AdminCourses.vue
index b27e6f6c6e6..425447b03d6 100644
--- a/resources/vue/components/AdminCourses.vue
+++ b/resources/vue/components/AdminCourses.vue
@@ -179,10 +179,11 @@ export default {
         },
         availableFields() {
             return Object.keys(this.fields).map(f => {
-                let state = this.activatedFields.includes(f);
                 return {
+                    type: 'checkbox',
                     label: this.fields[f],
-                    icon: state ? 'checkbox-checked' : 'checkbox-unchecked',
+                    checked: this.activatedFields.includes(f),
+                    name: 'activatedFields',
                     emit: 'toggleActiveField',
                     emitArguments: f,
                 }
diff --git a/resources/vue/components/StudipActionMenu.vue b/resources/vue/components/StudipActionMenu.vue
index c70d8edd55e..434336fbf8a 100644
--- a/resources/vue/components/StudipActionMenu.vue
+++ b/resources/vue/components/StudipActionMenu.vue
@@ -17,13 +17,21 @@
                         <span v-else class="action-menu-no-icon"></span>
                         {{ item.label }}
                     </a>
-                    <label v-else-if="item.icon" class="undecorated" v-bind="linkAttributes(item)" v-on="linkEvents(item)">
-                        <studip-icon :shape="item.icon.shape" :role="item.icon.role" :name="item.name" :title="item.label" v-bind="item.attributes ?? {}"></studip-icon>
+                    <label v-else-if="item.type === 'checkbox'" class="undecorated" v-on="linkEvents(item)">
+                        <studip-icon :shape="item.checked ? 'checkbox-checked' : 'checkbox-unchecked'" :role="item.icon.role" :name="item.name" :title="item.label" aria-role="checkbox" :aria-checked="item.checked.toString()" v-bind="linkAttributes(item)"></studip-icon>
+                        {{ item.label }}
+                    </label>
+                    <label v-else-if="item.type === 'radio'" class="undecorated" v-on="linkEvents(item)">
+                        <studip-icon :shape="item.checked ? 'radiobutton-checked' : 'radiobutton-unchecked'" :role="item.icon.role" :name="item.name" :title="item.label" aria-role="radio" :aria-checked="item.checked.toString()" v-bind="linkAttributes(item)"></studip-icon>
+                        {{ item.label }}
+                    </label>
+                    <label v-else-if="item.icon" class="undecorated" v-on="linkEvents(item)">
+                        <studip-icon :shape="item.icon.shape" :role="item.icon.role" :name="item.name" :title="item.label" v-bind="linkAttributes(item)"></studip-icon>
                         {{ item.label }}
                     </label>
                     <template v-else>
                         <span class="action-menu-no-icon"></span>
-                        <button :name="item.name" v-bind="Object.assign(item.attributes ?? {}, linkAttributes(item))" v-on="linkEvents(item)">
+                        <button :name="item.name" v-bind="linkAttributes(item)" v-on="linkEvents(item)">
                             {{ item.label }}
                         </button>
                     </template>
@@ -114,6 +122,7 @@ export default {
                     classes: classes.trim(),
                     attributes: item.attributes || {},
                     disabled: item.disabled,
+                    checked: item.checked,
                 };
             });
         },
-- 
GitLab