Skip to content

Commit

Permalink
Add a counter to filter button and tab (#826)
Browse files Browse the repository at this point in the history
* Add filter tab and filter button stories and tests

* Update audio count in search-types.spec.ts

* Update button snapshot

* Revert tab line-height changes

* Fix snapshots

* Add a counter to filter button and tab

* Add storybook tests for VFilterTab

* Fix tab focusing in test

* Remove duplicate key

* Update snapshots

* Revert unnecesary changes

* Update snapshots

* Update snapshot

* Add requested changes

* Use font-weight 400 for Clear filters

* Update snapshots

* Fix snapshot
  • Loading branch information
obulat authored Mar 20, 2023
1 parent 756b521 commit bee7999
Show file tree
Hide file tree
Showing 95 changed files with 22,157 additions and 6,508 deletions.
53 changes: 20 additions & 33 deletions frontend/src/components/VHeader/VFilterButton.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
<template>
<VButton
id="filter-button"
:variant="filtersAreApplied ? 'action-menu-muted' : 'action-menu'"
variant="plain"
size="disabled"
class="align-center label-regular h-12 w-12 gap-2 self-center xl:w-auto xl:ps-3"
:class="[filtersAreApplied ? 'xl:pe-3' : 'xl:pe-4']"
class="align-center label-regular h-12 w-12 gap-2 self-center border-tx xl:w-auto xl:ps-3 xl:pe-4"
:class="
pressed
? 'bg-dark-charcoal text-white hover:bg-dark-charcoal-90'
: 'bg-white hover:border-dark-charcoal-20'
"
:pressed="pressed"
:disabled="disabled"
aria-controls="filters"
:aria-label="xlMinLabel"
:aria-label="ariaLabel"
@click="$emit('toggle')"
@keydown.tab.exact="$emit('tab', $event)"
>
<VIcon
:class="filtersAreApplied ? 'hidden' : 'block'"
:icon-path="filterIcon"
<VFilterIconOrCounter
:applied-filter-count="filterCount"
:pressed="pressed"
/>
<span class="hidden xl:inline-block">{{ xlMinLabel }}</span>
<span class="xl:hidden" :class="{ hidden: !filtersAreApplied }">{{
lgMaxLabel
}}</span>
<span class="hidden xl:inline-block">{{ textLabel }}</span>
</VButton>
</template>

Expand All @@ -31,14 +31,12 @@ import { defineEvent } from "~/types/emits"
import { useI18n } from "~/composables/use-i18n"
import VButton from "~/components/VButton.vue"
import VIcon from "~/components/VIcon/VIcon.vue"
import filterIcon from "~/assets/icons/filter.svg"
import VFilterIconOrCounter from "~/components/VHeader/VFilterIconOrCounter.vue"
export default defineComponent({
name: "VFilterButton",
components: {
VIcon,
VFilterIconOrCounter,
VButton,
},
props: {
Expand All @@ -52,7 +50,6 @@ export default defineComponent({
},
},
emits: {
tab: defineEvent<[KeyboardEvent]>(),
toggle: defineEvent(),
},
setup() {
Expand All @@ -61,26 +58,16 @@ export default defineComponent({
const filterCount = computed(() => searchStore.appliedFilterCount)
const filtersAreApplied = computed(() => filterCount.value > 0)
/**
* This label's verbosity makes it useful for the aria-label
* where it is also used, especially on mobile where the
* label would just be the number of applied filters, and therefore
* basically useless as far as a label is concerned!
*/
const xlMinLabel = computed(() =>
filtersAreApplied.value
? i18n.tc("header.filter-button.with-count", filterCount.value)
: i18n.t("header.filter-button.simple")
)
const lgMaxLabel = computed(() =>
filtersAreApplied.value ? filterCount.value : ""
const textLabel = computed(() => i18n.t("header.filter-button.simple"))
const ariaLabel = computed(() =>
i18n.tc("header.filter-button.with-count", filterCount.value)
)
return {
filterIcon,
xlMinLabel,
lgMaxLabel,
ariaLabel,
textLabel,
filtersAreApplied,
filterCount,
}
},
})
Expand Down
40 changes: 40 additions & 0 deletions frontend/src/components/VHeader/VFilterIconOrCounter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<template>
<VIcon v-if="showIcon" :icon-path="filterIcon" />
<p
v-else
class="flex h-6 w-6 items-center justify-center rounded-sm"
:class="pressed ? 'bg-tx' : 'bg-dark-charcoal-10'"
>
{{ appliedFilterCount }}
</p>
</template>
<script lang="ts">
import { computed, defineComponent } from "@nuxtjs/composition-api"
import VIcon from "~/components/VIcon/VIcon.vue"
import filterIcon from "~/assets/icons/filter.svg"
export default defineComponent({
name: "VFilterIconOrCounter",
components: { VIcon },
props: {
appliedFilterCount: {
type: Number,
default: 0,
},
pressed: {
type: Boolean,
default: false,
},
},
setup(props) {
const showIcon = computed(() => props.appliedFilterCount === 0)
return {
showIcon,
filterIcon,
}
},
})
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@
id="content-settings"
size="medium"
class="gap-x-2 me-4"
><VIcon :icon-path="searchType.icon" />{{
$t("search-type.heading")
}}</VTab
><VIcon :icon-path="searchType.icon" />
<h2 class="label-regular">{{ $t("search-type.heading") }}</h2></VTab
>
<h2
v-else
Expand All @@ -32,14 +31,10 @@
<VIcon :icon-path="searchType.icon" />
{{ $t("search-type.heading") }}
</h2>
<VTab
<VFilterTab
v-if="showFilters"
id="filters"
size="medium"
class="label-regular gap-x-2"
>
<VIcon :icon-path="filtersIcon" />{{ $t("filters.title") }}</VTab
>
:applied-filter-count="appliedFilterCount"
/>
<VIconButton
class="self-center ms-auto hover:bg-dark-charcoal hover:text-white"
:icon-props="{ iconPath: closeIcon }"
Expand Down Expand Up @@ -68,9 +63,10 @@
<VButton
v-show="showClearFiltersButton"
variant="text"
class="!font-normal"
:disabled="isClearButtonDisabled"
@click="clearFilters"
>{{ clearFiltersLabel }}
>{{ $t("filter-list.clear") }}
</VButton>
<VShowResultsButton :is-fetching="isFetching" @click="close" />
</footer>
Expand All @@ -81,10 +77,10 @@ import { computed, defineComponent, ref } from "vue"
import { useSearchStore } from "~/stores/search"
import { useI18n } from "~/composables/use-i18n"
import useSearchType from "~/composables/use-search-type"
import VButton from "~/components/VButton.vue"
import VFilterTab from "~/components/VHeader/VHeaderMobile/VFilterTab.vue"
import VIcon from "~/components/VIcon/VIcon.vue"
import VIconButton from "~/components/VIconButton/VIconButton.vue"
import VModalContent from "~/components/VModal/VModalContent.vue"
Expand All @@ -96,14 +92,14 @@ import VTabPanel from "~/components/VTabs/VTabPanel.vue"
import VTabs from "~/components/VTabs/VTabs.vue"
import closeIcon from "~/assets/icons/close-small.svg"
import filtersIcon from "~/assets/icons/filter.svg"
export default defineComponent({
name: "VContentSettingsModalContent",
components: {
VIcon,
VModalContent,
VButton,
VFilterTab,
VIconButton,
VSearchGridFilter,
VSearchTypes,
Expand Down Expand Up @@ -135,8 +131,6 @@ export default defineComponent({
},
},
setup(props) {
const i18n = useI18n()
const searchStore = useSearchStore()
const content = useSearchType()
const selectedTab = ref<"content-settings" | "filters">("content-settings")
Expand All @@ -152,13 +146,8 @@ export default defineComponent({
const isClearButtonDisabled = computed(
() => !searchStore.isAnyFilterApplied
)
const appliedFilterCount = computed(() => searchStore.appliedFilterCount)
const clearFiltersLabel = computed(() =>
searchStore.isAnyFilterApplied
? i18n.t("filter-list.clear-numbered", {
number: appliedFilterCount.value,
})
: i18n.t("filter-list.clear")
const appliedFilterCount = computed<number>(
() => searchStore.appliedFilterCount
)
const searchType = computed(() => content.getSearchTypeProps())
Expand All @@ -169,7 +158,6 @@ export default defineComponent({
return {
closeIcon,
filtersIcon,
searchType,
selectedTab,
Expand All @@ -179,7 +167,6 @@ export default defineComponent({
appliedFilterCount,
showClearFiltersButton,
isClearButtonDisabled,
clearFiltersLabel,
clearFilters,
}
},
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/components/VHeader/VHeaderMobile/VFilterTab.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<template>
<VTab id="filters" size="medium" class="gap-x-2">
<VFilterIconOrCounter :applied-filter-count="appliedFilterCount" />
<h2 class="label-regular">{{ $t("filters.title") }}</h2>
</VTab>
</template>
<script lang="ts">
import VFilterIconOrCounter from "~/components/VHeader/VFilterIconOrCounter.vue"
import VTab from "~/components/VTabs/VTab.vue"
export default {
name: "VFilterTab",
components: { VFilterIconOrCounter, VTab },
props: {
appliedFilterCount: {
type: Number,
default: 0,
},
},
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
ArgsTable,
Canvas,
Description,
Meta,
Story,
} from "@storybook/addon-docs"
import VFilterTab from "~/components/VHeader/VHeaderMobile/VFilterTab.vue"
import VTab from "~/components/VTabs/VTab.vue"
import VTabs from "~/components/VTabs/VTabs.vue"

<Meta
title="Components/VHeader/VHeaderMobile/VFilterTab"
component={VFilterTab}
argTypes={{
appliedFilterCount: {
type: "number",
},
isSelected: {
type: "boolean",
},
}}
/>

export const Template = (args, { argTypes }) => ({
template: `
<div class="p-2">
<VTabs label="tabs"
:selected-id="args.selected" id="wrapper" variant="plain"
tablist-style="ps-6 pe-2 gap-x-4" class="flex min-h-0"
>
<template #tabs>
<VTab id="tab1" label="Tab 1" size="medium">Tab1</VTab>
<VFilterTab v-bind="args" v-on="args" />
</template>
</VTabs>
<div class="border-t border-dark-charcoal-20 h-2 w-full" />
</div>`,
components: { VFilterTab, VTabs, VTab },
props: Object.keys(argTypes),
setup() {
args["selected"] = args.isSelected ? "filters" : "tab1"
return { args }
},
})

# Filter tab

<Description of={VFilterTab} />

<ArgsTable of={VFilterTab} />

The tab button on the mobile modal that opens the filters tab. It shows how many
filters are applied, or a filters icon.

<Canvas>
<Story name="Default">{Template.bind({})}</Story>
</Canvas>
13 changes: 7 additions & 6 deletions frontend/src/components/VHeader/meta/VFilterButton.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,19 @@ import { IMAGE } from "~/constants/media"
appliedFilters: {
type: "number",
},
disabled: {
type: "boolean",
},
toggle: {
action: "toggle",
},
tab: {
action: "tab",
},
}}
/>

export const Template = (args, { argTypes }) => ({
template: `<VFilterButton v-bind="args" v-on="args" />`,
template: `<div id="wrapper" class="w-40 h-16 bg-dark-charcoal-06 flex align-center justify-center">
<VFilterButton v-bind="args" v-on="args" />
</div>`,
components: { VFilterButton },
props: Object.keys(argTypes),
setup() {
Expand Down Expand Up @@ -67,8 +69,7 @@ export const Template = (args, { argTypes }) => ({
<ArgsTable of={VFilterButton} />

The button opens and closes the filters sidebar. It also shows how many filters
are applied in the mobile view. the field receives an input. It also emits the
`search` event when the search button is clicked.
are applied. It also emits the `toggle` event when clicked.

<Canvas>
<Story
Expand Down
2 changes: 1 addition & 1 deletion frontend/test/playwright/e2e/filters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ breakpoints.describeMobileAndDesktop(() => {
const filterButtonText = await page
.locator('[aria-controls="filters"] span:visible')
.textContent()
expect(filterButtonText).toContain("1")
expect(filterButtonText).toContain("Filters")
} else {
const filtersAriaLabel =
(await page
Expand Down
2 changes: 1 addition & 1 deletion frontend/test/playwright/e2e/search-types.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const audioConfig = {
name: "Audio",
url: "/search/audio?q=birds",
canLoadMore: true,
results: /764 results/,
results: /Over 10,000 results/,
} as const

const searchTypes = [allContentConfig, imageConfig, audioConfig] as const
Expand Down
Binary file not shown.
Loading

0 comments on commit bee7999

Please sign in to comment.