From 372f97ab025cb3082a7cb6b842e5c15e0c64a734 Mon Sep 17 00:00:00 2001
From: Maurice <info@mauricehofman.com>
Date: Wed, 20 Jul 2022 15:30:51 +0200
Subject: [PATCH 1/2]  add-search: ability to filter searchRows and columns,
 give them the same styling

---
 js/Components/Table.vue             |  8 +++-
 js/Components/TableAddSearchRow.vue | 74 ++++++++++++++++++++++++-----
 js/Components/TableColumns.vue      | 50 ++++++++++++++++++-
 3 files changed, 117 insertions(+), 15 deletions(-)

diff --git a/js/Components/Table.vue b/js/Components/Table.vue
index 6e03de1..d5ebcb6 100644
--- a/js/Components/Table.vue
+++ b/js/Components/Table.vue
@@ -66,6 +66,7 @@
           :has-search-inputs-without-value="queryBuilderProps.hasSearchInputsWithoutValue"
           :search-inputs="queryBuilderProps.searchInputsWithoutGlobal"
           :on-add="showSearchInput"
+          :label="queryBuilderProps.globalSearch.label"
         >
           <TableAddSearchRow
             v-if="queryBuilderProps.hasSearchInputs"
@@ -73,6 +74,7 @@
             :search-inputs="queryBuilderProps.searchInputsWithoutGlobal"
             :has-search-inputs-without-value="queryBuilderProps.hasSearchInputsWithoutValue"
             :on-add="showSearchInput"
+            :label="queryBuilderProps.globalSearch.label"
           />
         </slot>
 
@@ -82,6 +84,7 @@
           :columns="queryBuilderProps.columns"
           :has-hidden-columns="queryBuilderProps.hasHiddenColumns"
           :on-change="changeColumnStatus"
+          :label="queryBuilderProps.globalSearch.label"
         >
           <TableColumns
             v-if="queryBuilderProps.hasToggleableColumns"
@@ -89,6 +92,7 @@
             :columns="queryBuilderProps.columns"
             :has-hidden-columns="queryBuilderProps.hasHiddenColumns"
             :on-change="changeColumnStatus"
+            :label="queryBuilderProps.globalSearch.label"
           />
         </slot>
       </div>
@@ -338,8 +342,8 @@ const resourceMeta = computed(() => {
 
     if("links" in props.resource && "meta" in props.resource) {
         if(Object.keys(props.resource.links).length === 4
-          && "next" in props.resource.links
-          && "prev" in props.resource.links) {
+            && "next" in props.resource.links
+            && "prev" in props.resource.links) {
             return {
                 ...props.resource.meta,
                 next_page_url: props.resource.links.next,
diff --git a/js/Components/TableAddSearchRow.vue b/js/Components/TableAddSearchRow.vue
index a4d523e..acc3fb6 100644
--- a/js/Components/TableAddSearchRow.vue
+++ b/js/Components/TableAddSearchRow.vue
@@ -27,23 +27,57 @@
       aria-labelledby="add-search-input-menu"
       class="min-w-max"
     >
-      <button
-        v-for="(searchInput, key) in searchInputs"
-        :key="key"
-        :dusk="`add-search-row-${searchInput.key}`"
-        class="text-left w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
-        role="menuitem"
-        @click.prevent="enableSearch(searchInput.key)"
-      >
-        {{ searchInput.label }}
-      </button>
+      <div class="relative">
+        <input
+          class="m-2 pl-9 text-sm rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300"
+          :placeholder="label"
+          :value="filter"
+          type="text"
+          name="global"
+          @input="event => filter = event.target.value"
+        >
+        <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            class="h-5 w-5 text-gray-400"
+
+            viewBox="0 0 20 20"
+            fill="currentColor"
+          >
+            <path
+              fill-rule="evenodd"
+              d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
+              clip-rule="evenodd"
+            />
+          </svg>
+        </div>
+      </div>
+
+      <div class="px-2">
+        <ul class="divide-y divide-gray-200">
+          <li
+            v-for="(searchInput, key) in filteredSearchInputs"
+            class="py-2 flex items-center justify-between"
+          >
+            <button
+              :key="key"
+              :dusk="`add-search-row-${searchInput.key}`"
+              class="text-left w-full px-4 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
+              role="menuitem"
+              @click.prevent="enableSearch(searchInput.key)"
+            >
+              {{ searchInput.label }}
+            </button>
+          </li>
+        </ul>
+      </div>
     </div>
   </ButtonWithDropdown>
 </template>
 
 <script setup>
 import ButtonWithDropdown from "./ButtonWithDropdown.vue";
-import { ref } from "vue"
+import {ref, computed} from 'vue';
 
 const props = defineProps({
     searchInputs: {
@@ -60,6 +94,12 @@ const props = defineProps({
         type: Function,
         required: true,
     },
+
+    label: {
+        type: String,
+        default: "Search...",
+        required: false,
+    },
 });
 
 const dropdown = ref(null)
@@ -68,4 +108,16 @@ function enableSearch(key) {
     props.onAdd(key);
     dropdown.value.hide()
 }
+
+const filter = ref(null);
+
+const filteredSearchInputs = computed(() => {
+    if(filter.value) {
+        return Object.values(props.searchInputs).filter(column => {
+            return column.label.toLowerCase().indexOf(filter.value.toLowerCase()) > -1;
+        });
+    }
+
+    return  props.searchInputs;
+});
 </script>
diff --git a/js/Components/TableColumns.vue b/js/Components/TableColumns.vue
index f762e1d..71c7568 100644
--- a/js/Components/TableColumns.vue
+++ b/js/Components/TableColumns.vue
@@ -30,16 +30,43 @@
       aria-labelledby="toggle-columns-menu"
       class="min-w-max"
     >
+
+      <div class="relative">
+        <input
+          class="m-2 pl-9 text-sm rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300"
+          :placeholder="label"
+          :value="filter"
+          type="text"
+          name="global"
+          @input="event => filter = event.target.value"
+        >
+        <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            class="h-5 w-5 text-gray-400"
+
+            viewBox="0 0 20 20"
+            fill="currentColor"
+          >
+            <path
+              fill-rule="evenodd"
+              d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
+              clip-rule="evenodd"
+            />
+          </svg>
+        </div>
+      </div>
+
       <div class="px-2">
         <ul class="divide-y divide-gray-200">
           <li
-            v-for="(column, key) in props.columns"
+            v-for="(column, key) in filteredColumns"
             v-show="column.can_be_hidden"
             :key="key"
             class="py-2 flex items-center justify-between"
           >
             <p
-              class="text-sm text-gray-900"
+              class="text-left w-full px-4 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
             >
               {{ column.label }}
             </p>
@@ -76,6 +103,7 @@
 
 <script setup>
 import ButtonWithDropdown from "./ButtonWithDropdown.vue";
+import {ref, computed} from 'vue';
 
 const props = defineProps({
     columns: {
@@ -92,5 +120,23 @@ const props = defineProps({
         type: Function,
         required: true,
     },
+
+    label: {
+        type: String,
+        default: "Search...",
+        required: false,
+    },
+});
+
+const filter = ref(null);
+
+const filteredColumns = computed(() => {
+    if(filter.value) {
+        return props.columns.filter(column => {
+            return column.label.toLowerCase().indexOf(filter.value.toLowerCase()) > -1;
+        });
+    }
+
+    return  props.columns;
 });
 </script>

From 5f86695b74d3c7defbe65cff23da3a1e5d522ebd Mon Sep 17 00:00:00 2001
From: Maurice <info@mauricehofman.com>
Date: Wed, 20 Jul 2022 16:16:24 +0200
Subject: [PATCH 2/2]  add-search: add overflow to ButtonWithDropdown for
 vertical scrolling when needded

---
 js/Components/TableAddSearchRow.vue | 76 ++++++++++++++++++-----------
 js/Components/TableColumns.vue      |  3 +-
 2 files changed, 48 insertions(+), 31 deletions(-)

diff --git a/js/Components/TableAddSearchRow.vue b/js/Components/TableAddSearchRow.vue
index acc3fb6..79d93a7 100644
--- a/js/Components/TableAddSearchRow.vue
+++ b/js/Components/TableAddSearchRow.vue
@@ -1,21 +1,24 @@
 <template>
   <ButtonWithDropdown
-    ref="dropdown"
-    dusk="add-search-row-dropdown"
-    :disabled="!hasSearchInputsWithoutValue"
-    class="w-auto"
+    placement="bottom-end"
+    dusk="columns-dropdown"
+    :active="hasHiddenColumns"
   >
     <template #button>
       <svg
         xmlns="http://www.w3.org/2000/svg"
-        class="h-5 w-5 text-gray-400"
-
+        class="h-5 w-5"
+        :class="{
+          'text-gray-400': !hasHiddenColumns,
+          'text-green-400': hasHiddenColumns,
+        }"
         viewBox="0 0 20 20"
         fill="currentColor"
       >
+        <path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
         <path
           fill-rule="evenodd"
-          d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
+          d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z"
           clip-rule="evenodd"
         />
       </svg>
@@ -24,7 +27,7 @@
     <div
       role="menu"
       aria-orientation="horizontal"
-      aria-labelledby="add-search-input-menu"
+      aria-labelledby="toggle-columns-menu"
       class="min-w-max"
     >
       <div class="relative">
@@ -54,19 +57,41 @@
       </div>
 
       <div class="px-2">
-        <ul class="divide-y divide-gray-200">
+        <ul class="divide-y overflow-auto max-h-56 divide-gray-200">
           <li
-            v-for="(searchInput, key) in filteredSearchInputs"
+            v-for="(column, key) in filteredColumns"
+            v-show="column.can_be_hidden"
+            :key="key"
             class="py-2 flex items-center justify-between"
           >
-            <button
-              :key="key"
-              :dusk="`add-search-row-${searchInput.key}`"
+            <p
               class="text-left w-full px-4 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
-              role="menuitem"
-              @click.prevent="enableSearch(searchInput.key)"
             >
-              {{ searchInput.label }}
+              {{ column.label }}
+            </p>
+
+            <button
+              type="button"
+              class="ml-4 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-light-blue-500"
+              :class="{
+                'bg-green-500': !column.hidden,
+                'bg-gray-200': column.hidden,
+              }"
+              :aria-pressed="!column.hidden"
+              :aria-labelledby="`toggle-column-${column.key}`"
+              :aria-describedby="`toggle-column-${column.key}`"
+              :dusk="`toggle-column-${column.key}`"
+              @click.prevent="onChange(column.key, column.hidden)"
+            >
+              <span class="sr-only">Column status</span>
+              <span
+                aria-hidden="true"
+                :class="{
+                  'translate-x-5': !column.hidden,
+                  'translate-x-0': column.hidden,
+                }"
+                class="inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"
+              />
             </button>
           </li>
         </ul>
@@ -80,17 +105,17 @@ import ButtonWithDropdown from "./ButtonWithDropdown.vue";
 import {ref, computed} from 'vue';
 
 const props = defineProps({
-    searchInputs: {
+    columns: {
         type: Object,
         required: true,
     },
 
-    hasSearchInputsWithoutValue: {
+    hasHiddenColumns: {
         type: Boolean,
         required: true,
     },
 
-    onAdd: {
+    onChange: {
         type: Function,
         required: true,
     },
@@ -102,22 +127,15 @@ const props = defineProps({
     },
 });
 
-const dropdown = ref(null)
-
-function enableSearch(key) {
-    props.onAdd(key);
-    dropdown.value.hide()
-}
-
 const filter = ref(null);
 
-const filteredSearchInputs = computed(() => {
+const filteredColumns = computed(() => {
     if(filter.value) {
-        return Object.values(props.searchInputs).filter(column => {
+        return props.columns.filter(column => {
             return column.label.toLowerCase().indexOf(filter.value.toLowerCase()) > -1;
         });
     }
 
-    return  props.searchInputs;
+    return  props.columns;
 });
 </script>
diff --git a/js/Components/TableColumns.vue b/js/Components/TableColumns.vue
index 71c7568..79d93a7 100644
--- a/js/Components/TableColumns.vue
+++ b/js/Components/TableColumns.vue
@@ -30,7 +30,6 @@
       aria-labelledby="toggle-columns-menu"
       class="min-w-max"
     >
-
       <div class="relative">
         <input
           class="m-2 pl-9 text-sm rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300"
@@ -58,7 +57,7 @@
       </div>
 
       <div class="px-2">
-        <ul class="divide-y divide-gray-200">
+        <ul class="divide-y overflow-auto max-h-56 divide-gray-200">
           <li
             v-for="(column, key) in filteredColumns"
             v-show="column.can_be_hidden"