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"