-
Notifications
You must be signed in to change notification settings - Fork 222
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(filter): added filter common component (#1027)
* feat(filter): added filter common component * refactor(lint): fixed lint errors * refactor(format): fixed formatting issues * feat(test): added test cases * refactor(lint): fixed formatting and lint * Remove unneeded test and comment out use for now * Minor fixes for frontend linting checks * Get rid of unused constant --------- Co-authored-by: Andrew Tavis McAllister <[email protected]>
- Loading branch information
1 parent
3387bed
commit a56ef26
Showing
9 changed files
with
389 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<template> | ||
<button | ||
:id="id" | ||
class="btn-base-class rounded-md xl:rounded-lg" | ||
:class="[ | ||
btnDynamicClass, | ||
!cta && 'style-cta bg-[#C8C8C8] hover:bg-[#C8C8C8]/70', | ||
cta && 'style-cta', | ||
]" | ||
:aria-label="$t(ariaLabel)" | ||
> | ||
<BtnIconsLabel | ||
:label="label" | ||
:hideLabelOnMobile="hideLabelOnMobile" | ||
:leftIcon="leftIcon" | ||
:rightIcon="rightIcon" | ||
:iconSize="iconSize" | ||
:counter="counter" | ||
/> | ||
</button> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import type { BtnAction } from "~/types/btn-props"; | ||
import { getBtnDynamicClass } from "~/utils/btnUtils"; | ||
const props = defineProps<BtnAction>(); | ||
const btnDynamicClass = getBtnDynamicClass( | ||
props.cta, | ||
props.fontSize, | ||
props.isDisabled | ||
); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
<template> | ||
<div class="card-style flex flex-col px-3 py-4 md:px-5"> | ||
<div class="relative flex w-full flex-col"> | ||
<!-- Header with Tabs --> | ||
<div class="mb-4 flex items-center justify-between"> | ||
<h2 class="text-2xl font-semibold">Filter</h2> | ||
<div | ||
class="inline-flex overflow-hidden rounded-lg border border-primary-text" | ||
> | ||
<button | ||
v-for="(tab, index) in tabs" | ||
@click="activeTab = tab.id" | ||
:key="tab.id" | ||
:class="[ | ||
'px-4 py-2', | ||
activeTab === tab.id | ||
? 'bg-cta-orange text-primary-text' | ||
: 'bg-[#C8C8C8] text-primary-text hover:bg-[#C8C8C8]/70', | ||
index !== tabs.length - 1 ? 'border-r border-primary-text' : '', | ||
]" | ||
> | ||
{{ tab.name }} | ||
</button> | ||
</div> | ||
</div> | ||
|
||
<!-- Search Input --> | ||
<div | ||
class="elem-shadow-sm flex select-none items-center justify-between rounded-md bg-layer-2 py-1 pl-[12px] text-left text-distinct-text transition duration-200 focus-within:mb-[-3px] focus-within:border-2 focus-within:border-link-text" | ||
> | ||
<div class="flex flex-1 items-center space-x-2 pl-1"> | ||
<Icon | ||
class="my-1 h-4 w-4 flex-shrink-0" | ||
:name="IconMap.SEARCH" | ||
size="1em" | ||
/> | ||
<div class="min-w-0 flex-1"> | ||
<label for="input-search" class="sr-only">{{ | ||
"Search to filter" | ||
}}</label> | ||
<input | ||
v-model="searchQuery" | ||
@focus="onFocus" | ||
@blur="onFocusLost" | ||
ref="input" | ||
id="input-search" | ||
class="w-full text-ellipsis bg-transparent py-2 outline-none" | ||
type="text" | ||
placeholder="Search to filter" | ||
/> | ||
</div> | ||
</div> | ||
<div | ||
ref="hotkeyIndicators" | ||
class="transition-duration-200 flex flex-shrink-0 space-x-1 pr-1 transition-opacity" | ||
> | ||
<div | ||
class="has-tooltip flex rounded-md bg-highlight px-2 py-[0.125rem] text-center text-sm text-distinct-text" | ||
> | ||
<TooltipBase | ||
class="invisible -mt-8" | ||
:text="$t('components._global.slash_tooltip_label')" | ||
/> | ||
<p class="-mt-[0.075rem]">/</p> | ||
</div> | ||
<div | ||
v-if="isMacOS" | ||
class="has-tooltip flex rounded-md bg-highlight px-2 py-[0.125rem] text-center text-sm text-distinct-text" | ||
> | ||
<TooltipBase | ||
class="invisible -mt-8" | ||
:text="$t('components._global.command_tooltip_label')" | ||
/> | ||
<p>⌘k</p> | ||
</div> | ||
<div | ||
v-else | ||
class="has-tooltip flex rounded-md bg-highlight px-2 py-[0.125rem] text-center text-sm text-distinct-text" | ||
> | ||
<TooltipBase | ||
class="invisible -mt-8" | ||
:text="$t('components._global.control_tooltip_label')" | ||
/> | ||
<p>⌃k</p> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<!-- Tag Sections --> | ||
<div | ||
v-for="(section, sectionIndex) in sections" | ||
:key="sectionIndex" | ||
class="mt-4" | ||
> | ||
<h3 class="mb-3 flex items-center gap-2"> | ||
<span | ||
class="font-redhat flex items-center text-[22px] font-[600] leading-[29.11px]" | ||
> | ||
{{ section.title }} | ||
<Icon | ||
v-if="section.icon" | ||
:name="section.icon" | ||
class="ml-2 h-4 w-4 text-primary-text" | ||
/> | ||
</span> | ||
</h3> | ||
<!-- <GridFilterTags :tags="['Berlin', 'Activism', 'Eco']" class="mt-3" /> --> | ||
<div class="flex flex-wrap gap-2"> | ||
<BtnTag | ||
v-for="tag in section.tags" | ||
@click="toggleTag(tag)" | ||
@keydown.enter="toggleTag(tag)" | ||
:key="tag.id" | ||
class="flex max-h-[40px]" | ||
:cta="tag.selected" | ||
:label="tag.name" | ||
fontSize="sm" | ||
:ariaLabel="$t(tag.name)" | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { useMagicKeys, whenever } from "@vueuse/core"; | ||
import { ref } from "vue"; | ||
import { IconMap } from "~/types/icon-map"; | ||
interface Tag { | ||
id: number; | ||
name: string; | ||
selected: boolean; | ||
} | ||
interface TagSection { | ||
title: string; | ||
tags: Tag[]; | ||
icon?: string; | ||
} | ||
interface Tab { | ||
id: string; | ||
name: string; | ||
} | ||
const props = defineProps<{ | ||
sections: TagSection[]; | ||
tabs: Tab[]; | ||
}>(); | ||
const { isMacOS } = useDevice(); | ||
const searchQuery = ref(""); | ||
const input = ref(); | ||
const hotkeyIndicators = ref(); | ||
const isInputFocused = ref(false); | ||
const { slash } = useMagicKeys({ | ||
passive: false, | ||
onEventFired(e) { | ||
if (e.key === "/" && e.type === "keydown") e.preventDefault(); | ||
}, | ||
}); | ||
whenever(slash, () => { | ||
setTimeout(() => { | ||
if (input.value) { | ||
input.value.focus(); | ||
} | ||
}, 0); | ||
}); | ||
const onFocus = () => { | ||
hotkeyIndicators.value.classList.add("hide"); | ||
setTimeout(() => { | ||
isInputFocused.value = true; | ||
hotkeyIndicators.value.classList.add("hidden"); | ||
}, 200); | ||
}; | ||
const onFocusLost = () => { | ||
hotkeyIndicators.value.classList.remove("hidden"); | ||
isInputFocused.value = false; | ||
setTimeout(() => { | ||
hotkeyIndicators.value.classList.remove("hide"); | ||
}, 200); | ||
}; | ||
const emit = defineEmits<{ | ||
"filter-change": [ | ||
{ | ||
search: string; | ||
activeTab: string; | ||
selectedTags: number[]; | ||
}, | ||
]; | ||
}>(); | ||
const toggleTag = (tag: Tag) => { | ||
tag.selected = !tag.selected; | ||
emitChange(); | ||
}; | ||
const emitChange = () => { | ||
emit("filter-change", { | ||
search: searchQuery.value, | ||
activeTab: activeTab.value, | ||
selectedTags: props.sections[0].tags | ||
.filter((t) => t.selected) | ||
.map((t) => t.id), | ||
}); | ||
}; | ||
watch(searchQuery, () => { | ||
emitChange(); | ||
}); | ||
const activeTab = ref(props.tabs[0]?.id || ""); | ||
watch(activeTab, () => { | ||
emitChange(); | ||
}); | ||
</script> | ||
|
||
<style scoped> | ||
.hide { | ||
opacity: 0; | ||
} | ||
.search-enter-active { | ||
transition: opacity 0.25s ease; | ||
transition-delay: 0.125s; | ||
} | ||
.search-leave-active { | ||
transition: opacity 0.25s ease; | ||
} | ||
.search-enter-from, | ||
.search-leave-to { | ||
opacity: 0; | ||
} | ||
.search-enter-from { | ||
transition-delay: 0.25s; | ||
} | ||
.shortcuts-enter-active { | ||
transition: opacity 0.25s ease; | ||
transition-delay: 0.375s; | ||
} | ||
.shortcuts-leave-active { | ||
transition: opacity 0.125s ease; | ||
} | ||
.shortcuts-enter-from, | ||
.shortcuts-leave-to { | ||
opacity: 0; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.