<template> <div class="filters-frame"> <div v-show="showFilters" class="filters-container" :class="{ show: showFiltersCompact }" @click="toggleFilters(false)" > <div class="filters-sidebar"> <form class="filters" :class="{ loading }" @submit.prevent @click.stop > <slot /> </form> <button v-if="typeof results === 'number'" class="button results" >Show {{ results }} {{ results > 1 ? 'results' : 'result' }}</button> </div> <div class="filters-toggle close" :class="{ 'show-full': showFilters, 'show-compact': showFiltersCompact }" @click.stop="toggleFilters(false)" > <Icon icon="arrow-left3" class="arrow" /> <Icon icon="cross2" class="cross" /> </div> </div> <div class="filters-toggle open" :class="{ 'show-full': !showFilters, 'show-compact': !showFiltersCompact }" @click.stop="toggleFilters(true)" ><Icon icon="filter" /></div> </div> </template> <script setup> import { ref } from 'vue'; defineProps({ results: { type: Number, default: null, }, loading: { type: Boolean, default: false, }, }); // desktop defaults to open, compact defaults to closed // we can't measure viewframe in SSR, so use separate toggles const showFilters = ref(true); const showFiltersCompact = ref(false); function toggleFilters(state) { showFilters.value = state ?? !showFilters.value; showFiltersCompact.value = state ?? !showFiltersCompact.value; } </script> <style> .filter { padding: .5rem; .input { width: 100%; } &:not(:last-child) { border-bottom: solid 1px var(--shadow-weak-30); } } .filter.search-container { display: flex; align-items: stretch; .input { flex-grow: 1; } .icon { height: auto; display: flex; align-items: center; padding-left: .5rem; fill: var(--shadow); &:hover { cursor: pointer; fill: var(--primary); } } } .filter-mode { width: 100%; color: var(--shadow); background: none; padding: .75rem; margin: 0 0 .5rem 0; font-size: 1rem; border: none; border-bottom: solid 1px var(--shadow-hint); option { color: var(--text-dark); } } .filters-sort { display: flex; border-bottom: solid 1px var(--shadow-weak-30); } .filters-search { flex-grow: 1; border-bottom: none; width: 0; } .filter-clear { display: flex; align-items: center; justify-content: space-between; padding: .5rem 1rem; color: var(--shadow-weak-10); text-decoration: none; cursor: default; .icon { fill: var(--shadow-weak-30); margin: 0 0 0 1rem; } &.active { color: var(--shadow); .icon { fill: var(--shadow-weak-10); } &:hover { color: var(--text); background: var(--shadow-hint); cursor: pointer; .icon { fill: var(--alert); } } } } .filter-items { max-height: 15rem; overflow-y: auto; &.selected { box-shadow: 0 -1px 3px var(--shadow-weak-30); } &.available { box-shadow: inset 0 -1px 3px var(--shadow-weak-30); } } .filter-items .filter-item { display: flex; align-items: stretch; &:hover { background: var(--shadow-weak-30); cursor: pointer; } &.selected { .filter-include { .filter-add { fill: var(--success); } &:hover { .filter-add { display: none; } .filter-remove { display: inline-block; } } } } &.disabled { opacity: .5; pointer-events: none; .filter-include { visibility: hidden; } } } .filter-name { min-width: 8rem; display: flex; justify-content: space-between; align-items: center; flex-grow: 1; padding: .25rem 0 .25rem .25rem; color: var(--text); text-decoration: none; } .filter-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .filter-include { display: flex; align-items: center; .icon { width: 1rem; padding: 0 .75rem; fill: var(--shadow-weak-30); } .filter-remove { display: none; fill: var(--alert); } &:hover { cursor: pointer; } } .filter-include:hover, .filter-name:hover { background: var(--shadow-weak-30); } .filter-sort { display: flex; flex-shrink: 0; align-items: center; align-self: stretch; justify-content: center; padding: 0 .25rem; cursor: pointer; font-weight: bold; color: var(--shadow); &.order { padding: 0 .5rem 0 .25rem; } .icon { fill: var(--shadow); } &:hover { color: var(--primary); .icon { fill: var(--primary); } } } .filter-details { display: flex; align-items: stretch; margin-left: .5rem; .filter-remove.icon { padding: .25rem .6rem; fill: var(--shadow); &:hover { fill: var(--alert); } } } .filter-count { width: 1.75rem; display: flex; align-items: center; justify-content: flex-end; padding: 0 .25rem; overflow: hidden; color: var(--shadow-weak-10); font-size: .9rem; } .filter-empty { padding: .5rem; color: var(--shadow); font-style: italic; } .filter-disclaimer { background: var(--notice); color: var(--highlight-strong-30); padding: .25rem .5rem; box-shadow: inset 0 0 3px var(--shadow-weak-30); line-height: 1.25; font-size: .9rem; } @media (--compact) { .filter { border-right: solid 1px var(--shadow-weak-30); } } </style> <style scoped> .filters-frame { position: relative; } .filters-container { display: flex; height: 100%; } .filters-sidebar { height: 100%; display: flex; flex-direction: column; background: var(--background); } .filters { width: 17rem; flex-grow: 1; position: relative; box-sizing: border-box; padding: 0 0 .5rem 0; background: var(--background); box-shadow: 0 0 3px var(--shadow-weak-30); &::-webkit-scrollbar { display: none; } } .filters-toggle { min-width: 2rem; height: 2.5rem; display: none; justify-content: center; align-items: center; padding: 0 .25rem; position: absolute; top: .35rem; right: -2.5rem; border-radius: 0 .5rem .5rem 0; background: var(--background); color: var(--shadow); font-weight: bold; box-shadow: inset 0 0 3px var(--shadow-weak-30); &.open { left: 0; right: auto; } &.show-full { display: flex; } &.close .cross { display: none; } .icon { fill: var(--shadow); } &:hover { cursor: pointer; .icon { fill: var(--primary); } } } .results { display: none; justify-content: center; background: var(--primary); color: var(--text-light); border-radius: 0; } .loading { opacity: .3; pointer-events: none; } @media (--compact) { .filters-container { display: none; height: 100%; width: 100%; position: fixed; top: 0; left: 0; z-index: 500; background: var(--background-dim); &.show { display: flex; } } .filters { width: 20rem; border-right: solid 1.5rem var(--background); overflow-y: auto; } .filters-toggle { &.show-full { display: none; } &.show-compact { display: flex; &.close { background: var(--grey-light-40); position: relative; right: 0; } } &.close .cross { display: flex; } &.close .arrow { display: none; } } .compact-hide { display: none; } .results { display: flex; } } </style>