feat(explorer): redesign grid header according to v2 design

This commit is contained in:
Daniel Klein 2024-09-03 17:13:23 +02:00
parent 8b9a1e0071
commit 441bff9017
6 changed files with 187 additions and 69 deletions

View file

@ -5,6 +5,7 @@ const props = defineProps<{
options: InputOption[] options: InputOption[]
modelValue: string modelValue: string
count?: number count?: number
titleShowCount?: boolean
}>() }>()
const emits = defineEmits(['update:modelValue', 'selected']) const emits = defineEmits(['update:modelValue', 'selected'])
@ -23,15 +24,18 @@ function onOptionSelected(value: string) {
v-model="selectedValue" v-model="selectedValue"
as="div" as="div"
> >
<div class="relative mt-2 font-700 font-24px"> <div class="relative font-700 font-24px">
<HeadlessListboxButton <HeadlessListboxButton
class="relative w-full cursor-pointer py-8px p-16px text-left border-2px text-app-white sm:text-sm sm:leading-6" class="relative cursor-pointer py-6px px-14px text-left border-2px bg-app-white text-app-black sm:text-sm sm:leading-6"
> >
<span class="block truncate mr-8px">{{ isOptionSelected?.label }} <span opacity-50>({{ isOptionSelected?.count }})</span></span> <span class="block truncate mr-8px font">{{ isOptionSelected?.label }} <span
v-if="titleShowCount"
opacity-50
>({{ isOptionSelected?.count }})</span></span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> <span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<UnoIcon <UnoIcon
i-heroicons-solid-chevron-down i-heroicons-solid-chevron-down
text-app-white text-app-black
/> />
</span> </span>
</HeadlessListboxButton> </HeadlessListboxButton>
@ -42,7 +46,7 @@ function onOptionSelected(value: string) {
leave-to-class="opacity-0" leave-to-class="opacity-0"
> >
<HeadlessListboxOptions <HeadlessListboxOptions
class="absolute z-100 max-h-60 w-full divide-y-2px border-2px border-t-0 overflow-auto bg-app-black text-app-white focus:outline-none sm:text-sm" class="absolute z-100 max-h-60 w-auto border-2px border-t-0 overflow-auto bg-app-black text-app-white focus:outline-none sm:text-sm"
> >
<HeadlessListboxOption <HeadlessListboxOption
v-for="option in props.options" v-for="option in props.options"
@ -53,15 +57,15 @@ function onOptionSelected(value: string) {
@click="onOptionSelected(option.value)" @click="onOptionSelected(option.value)"
> >
<li <li
class="w-full relative cursor-pointer select-none py-8px p-16px" class="w-full relative cursor-pointer select-none py-8px p-16px bg-app-black text-app-white"
:class="[active ? 'bg-#ffffff1a' : 'text-white']" :class="[active ? 'text-app-white' : 'text-app-white']"
> >
<span <span
class="block truncate" class="block truncate"
:class="[selected ? 'font-semibold' : 'font-normal']" :class="[selected ? 'font-semibold' : 'font-normal']"
> >
{{ option.label }} {{ option.label }}
<span opacity-50>({{ option.count }})</span> <span v-if="titleShowCount" opacity-50>({{ option.count }})</span>
</span> </span>
</li> </li>
</HeadlessListboxOption> </HeadlessListboxOption>

View file

@ -11,6 +11,16 @@ const displayedProjects = computed(() => props.projects.slice(0, displayCount.va
function showMoreProjects() { function showMoreProjects() {
displayCount.value += 50 displayCount.value += 50
} }
const cardTitles = ref< { label: string, togglable?: boolean, toggled?: boolean }[]>([
{ label: 'Usecase' },
{ label: 'Openess', togglable: true },
{ label: 'Technology', togglable: true },
{ label: 'Privacy', togglable: true },
{ label: 'Ecosystem' },
{ label: 'Links' },
{ label: 'W3PN Score', togglable: true },
])
</script> </script>
<template> <template>
@ -19,6 +29,118 @@ function showMoreProjects() {
flex-col flex-col
items-start items-start
> >
<div
v-if="displayedProjects.length"
flex
items-center
gap-x-12px
w-full
mb="8px md:16px"
>
<h2
text="app-white 16px md:24px"
font-700
leading="24px md:32px"
whitespace-nowrap
>
24 Defi
</h2>
<div
h-2px
w-full
bg-app-white
/>
<button
type="button"
i-heroicons-solid-chevron-down
text="app-white 24px"
/>
</div>
<div
v-if="displayedProjects.length"
flex
items-center
justify-between
w-full
mb-16px
>
<div
flex
items-center
gap-4px
>
<p
text="12px md:14px"
leading="16px md:24px"
whitespace-nowrap
>
Project name
</p>
<button
type="button"
i-heroicons-solid-chevron-down
text="app-white 20px"
/>
</div>
<div
hidden
md:flex
items-center
justify-end
gap-48px
w-full
>
<div
v-for="title in cardTitles"
:key="title.label"
flex
items-center
gap-4px
>
<p
text="12px md:14px"
leading="16px md:24px"
whitespace-nowrap
>
{{ title.label }}
</p>
<button
v-if="title.togglable"
type="button"
:class="[title.toggled
? 'i-heroicons-solid-chevron-up'
: 'i-heroicons-solid-chevron-down']"
text="app-white 20px"
@click="title.toggled = !title.toggled"
/>
</div>
</div>
<div
flex
items-center
gap-4px
md:hidden
>
<p
text="12px md:14px"
leading="16px md:24px"
>
Sort by:
</p>
<p
text="12px md:14px"
leading="16px md:24px"
font-700
>
Score
</p>
<button
type="button"
i-heroicons-solid-chevron-down
text="app-white 20px"
/>
</div>
</div>
<div <div
v-if="displayedProjects.length" v-if="displayedProjects.length"
grid grid

View file

@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { InputOption } from '~/types' import type { InputOption } from '~/types'
defineProps<{ placeholder?: string, includeSort?: boolean }>()
const { filter, switcher } = storeToRefs(useData()) const { filter, switcher } = storeToRefs(useData())
const options: InputOption[] = [ const options: InputOption[] = [
{ label: 'A to Z', value: 'atoz' }, { label: 'A to Z', value: 'atoz' },
@ -22,8 +24,8 @@ const isSearchFocused = ref(false)
border-2px border-2px
flex flex
items-center items-center
max-w-320px
w-full w-full
h-40px
hover:opacity-100 hover:opacity-100
:class="isSearchFocused ? 'opacity-100' : 'opacity-25'" :class="isSearchFocused ? 'opacity-100' : 'opacity-25'"
> >
@ -34,7 +36,7 @@ const isSearchFocused = ref(false)
w-fit w-fit
> >
<UnoIcon <UnoIcon
i-web-search i-heroicons-solid-magnifying-glass
text-16px text-16px
:class="isSearchFocused ? 'opacity-100' : 'opacity-50' " :class="isSearchFocused ? 'opacity-100' : 'opacity-50' "
class="uno-icon" class="uno-icon"
@ -42,6 +44,7 @@ const isSearchFocused = ref(false)
</div> </div>
<input <input
v-model="filter.query" v-model="filter.query"
:placeholder="placeholder"
type="text" type="text"
bg-transparent bg-transparent
border-transparent border-transparent
@ -54,7 +57,7 @@ const isSearchFocused = ref(false)
@blur="isSearchFocused = false" @blur="isSearchFocused = false"
> >
</div> </div>
<div> <div v-if="includeSort">
<div <div
flex flex
gap-24px gap-24px

View file

@ -3,7 +3,7 @@ import type { Category, Project, ProjectShallow } from '~/types'
export const useData = defineStore('data', () => { export const useData = defineStore('data', () => {
const categories = useState<Category[]>('categories') const categories = useState<Category[]>('categories')
const projects = useState<Project[]>('projects') const projects = useState<Project[]>('projects')
const selectedCategoryId = useState(() => 'defi') const selectedCategoryId = useState(() => 'all')
const filter = reactive({ const filter = reactive({
query: '', query: '',
sortby: 'atoz', sortby: 'atoz',

View file

@ -3,8 +3,14 @@ import type { InputOption } from '~/types'
const { categories, filteredProjectsCount, selectedCategoryId } = storeToRefs(useData()) const { categories, filteredProjectsCount, selectedCategoryId } = storeToRefs(useData())
const selectedUsecaseId = ref('usecase')
const selectedEcosystemId = ref('ecosystem')
const selectedAssetsUsedId = ref('assetsUsed')
const selectedFeaturesId = ref('features')
const categoriesOptions = ref(categories.value ? categories.value.map(c => ({ label: c.name, value: c.id, count: c.projectsCount })) : []) const categoriesOptions = ref(categories.value ? categories.value.map(c => ({ label: c.name, value: c.id, count: c.projectsCount })) : [])
const extendedOptions: InputOption[] = [ const extendedOptions: InputOption[] = [
{ label: 'Category', value: 'all' },
...categoriesOptions.value, ...categoriesOptions.value,
] ]
@ -56,77 +62,58 @@ watch([scrollY, top, y], (newValues, oldValues) => {
w-full w-full
xl:gap-32px xl:gap-32px
> >
<div w-fit>
<div
ref="scrollEl"
class="no-scrollbar"
h-100vh
overflow-y-auto
sticky
top-32px
hidden
xl:block
min-w-234px
pb-48px
>
<Category
v-for="category in sortedFilteredCategories"
:key="category.id"
:title="category.name"
:count="category.projectsCount"
:selected="selectedCategoryId === category.id"
@click="[navigateTo(`/category/${category.id}`), selectedCategoryId = category.id]"
/>
</div>
</div>
<div w-full>
<div <div
flex flex
flex-col flex-col
gap-16px
w-full w-full
> >
<div <div
xl:hidden flex
block flex-col
md:flex-row
md:justify-between
md:items-center
gap-16px
mb="16px md:32px"
> >
<h2 <SearchBox
text-14px flex-1
font-700 :placeholder="`Search in ${filteredProjectsCount} Projects`"
/>
<div
md:flex-2
flex
items-center
gap-16px
overflow-x-auto
> >
Choose category
</h2>
<CategorySelectBox <CategorySelectBox
v-model="selectedCategoryId" v-model="selectedCategoryId"
:options="extendedOptions" :options="extendedOptions"
w-full w-full
@selected="selectedCategoryId === 'all' ? navigateTo(`/`) : navigateTo(`/category/${selectedCategoryId}`)" @selected="selectedCategoryId === 'all' ? navigateTo(`/`) : navigateTo(`/category/${selectedCategoryId}`)"
/> />
</div> <CategorySelectBox
<SearchBox /> v-model="selectedUsecaseId"
</div> :options="[{ label: 'Usecase', value: 'usecase' }]"
<div w-full
flex
gap-28px
items-center
my-24px
mt-28px
>
<h2
v-if="selectedCategoryId"
w-max
font-700
text-18px
sm:text-28px
whitespace-nowrap
>
{{ selectedCategoryId === 'all' ? `${filteredProjectsCount} All Projects` : `${filteredProjectsCount ?? 0} ${selectedCategory?.name}` }}
</h2>
<div
h-2px
w="full"
bg-white
/> />
<CategorySelectBox
v-model="selectedEcosystemId"
:options="[{ label: 'Ecosystem', value: 'ecosystem' }]"
w-full
/>
<CategorySelectBox
v-model="selectedAssetsUsedId"
:options="[{ label: 'Assets used', value: 'assetsUsed' }]"
w-full
/>
<CategorySelectBox
v-model="selectedFeaturesId"
:options="[{ label: 'Features', value: 'features' }]"
w-full
/>
</div>
</div> </div>
<slot /> <slot />
</div> </div>

View file

@ -35,5 +35,7 @@ export const collections = {
open1: '<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="gray" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"/></svg>', open1: '<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="gray" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"/></svg>',
matrix: '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none"><path d="M1.266 1.10241V46.8989H4.5615V48.0014H0V0.00141309H4.5615V1.10391L1.266 1.10241ZM15.3525 15.6179V17.9384H15.414C16.032 17.0459 16.782 16.3679 17.6475 15.8819C18.516 15.3974 19.524 15.1559 20.6475 15.1559C21.726 15.1559 22.7175 15.3674 23.61 15.7814C24.5085 16.2029 25.1805 16.9454 25.6485 17.9999C26.157 17.2499 26.853 16.5854 27.7185 16.0154C28.587 15.4454 29.619 15.1559 30.813 15.1559C31.719 15.1559 32.5545 15.2654 33.3285 15.4919C34.11 15.7094 34.767 16.0619 35.3205 16.5464C35.868 17.0399 36.297 17.6729 36.6105 18.4544C36.915 19.2344 37.071 20.1809 37.071 21.2909V32.7434H32.3775V23.0414C32.3775 22.4699 32.3535 21.9239 32.3055 21.4154C32.2755 20.9549 32.157 20.5094 31.947 20.0954C31.7505 19.7189 31.4475 19.4069 31.071 19.2059C30.6885 18.9779 30.1575 18.8699 29.5005 18.8699C28.836 18.8699 28.305 18.9944 27.8985 19.2434C27.501 19.4939 27.165 19.8374 26.9385 20.2439C26.697 20.6744 26.541 21.1499 26.4765 21.6344C26.3985 22.1579 26.361 22.6829 26.352 23.2064V32.7449H21.6555V23.1434C21.6555 22.6349 21.648 22.1354 21.618 21.6434C21.6015 21.1679 21.5085 20.7074 21.3285 20.2694C21.1725 19.8479 20.877 19.4954 20.5005 19.2614C20.118 19.0109 19.5465 18.8789 18.7965 18.8789C18.57 18.8789 18.2745 18.9254 17.9145 19.0274C17.5545 19.1279 17.196 19.3154 16.86 19.5899C16.5165 19.8719 16.218 20.2694 15.9765 20.7854C15.735 21.2999 15.618 21.9794 15.618 22.8239V32.7539H10.9215V15.6224L15.3525 15.6179ZM46.734 46.8974V1.10091H43.4385V-0.00158691H48V47.9984H43.4385V46.8959L46.734 46.8974Z" fill="white"/></svg>', matrix: '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none"><path d="M1.266 1.10241V46.8989H4.5615V48.0014H0V0.00141309H4.5615V1.10391L1.266 1.10241ZM15.3525 15.6179V17.9384H15.414C16.032 17.0459 16.782 16.3679 17.6475 15.8819C18.516 15.3974 19.524 15.1559 20.6475 15.1559C21.726 15.1559 22.7175 15.3674 23.61 15.7814C24.5085 16.2029 25.1805 16.9454 25.6485 17.9999C26.157 17.2499 26.853 16.5854 27.7185 16.0154C28.587 15.4454 29.619 15.1559 30.813 15.1559C31.719 15.1559 32.5545 15.2654 33.3285 15.4919C34.11 15.7094 34.767 16.0619 35.3205 16.5464C35.868 17.0399 36.297 17.6729 36.6105 18.4544C36.915 19.2344 37.071 20.1809 37.071 21.2909V32.7434H32.3775V23.0414C32.3775 22.4699 32.3535 21.9239 32.3055 21.4154C32.2755 20.9549 32.157 20.5094 31.947 20.0954C31.7505 19.7189 31.4475 19.4069 31.071 19.2059C30.6885 18.9779 30.1575 18.8699 29.5005 18.8699C28.836 18.8699 28.305 18.9944 27.8985 19.2434C27.501 19.4939 27.165 19.8374 26.9385 20.2439C26.697 20.6744 26.541 21.1499 26.4765 21.6344C26.3985 22.1579 26.361 22.6829 26.352 23.2064V32.7449H21.6555V23.1434C21.6555 22.6349 21.648 22.1354 21.618 21.6434C21.6015 21.1679 21.5085 20.7074 21.3285 20.2694C21.1725 19.8479 20.877 19.4954 20.5005 19.2614C20.118 19.0109 19.5465 18.8789 18.7965 18.8789C18.57 18.8789 18.2745 18.9254 17.9145 19.0274C17.5545 19.1279 17.196 19.3154 16.86 19.5899C16.5165 19.8719 16.218 20.2694 15.9765 20.7854C15.735 21.2999 15.618 21.9794 15.618 22.8239V32.7539H10.9215V15.6224L15.3525 15.6179ZM46.734 46.8974V1.10091H43.4385V-0.00158691H48V47.9984H43.4385V46.8959L46.734 46.8974Z" fill="white"/></svg>',
news: '<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28" fill="none"><g opacity="1"><mask id="mask0_2258_10849" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="28" height="28"><rect width="28" height="28" fill="#D9D9D9"/></mask><g mask="url(#mask0_2258_10849)"><path d="M4.66146 24.5C4.01979 24.5 3.47049 24.2715 3.01354 23.8146C2.5566 23.3576 2.32812 22.8083 2.32812 22.1667V5.83333C2.32812 5.19167 2.5566 4.64236 3.01354 4.18542C3.47049 3.72847 4.01979 3.5 4.66146 3.5H23.3281C23.9698 3.5 24.5191 3.72847 24.976 4.18542C25.433 4.64236 25.6615 5.19167 25.6615 5.83333V22.1667C25.6615 22.8083 25.433 23.3576 24.976 23.8146C24.5191 24.2715 23.9698 24.5 23.3281 24.5H4.66146ZM4.66146 22.1667H23.3281V5.83333H4.66146V22.1667ZM6.99479 19.8333H20.9948V17.5H6.99479V19.8333ZM6.99479 15.1667H11.6615V8.16667H6.99479V15.1667ZM13.9948 15.1667H20.9948V12.8333H13.9948V15.1667ZM13.9948 10.5H20.9948V8.16667H13.9948V10.5Z" fill="white"/></g></g></svg>', news: '<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28" fill="none"><g opacity="1"><mask id="mask0_2258_10849" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="28" height="28"><rect width="28" height="28" fill="#D9D9D9"/></mask><g mask="url(#mask0_2258_10849)"><path d="M4.66146 24.5C4.01979 24.5 3.47049 24.2715 3.01354 23.8146C2.5566 23.3576 2.32812 22.8083 2.32812 22.1667V5.83333C2.32812 5.19167 2.5566 4.64236 3.01354 4.18542C3.47049 3.72847 4.01979 3.5 4.66146 3.5H23.3281C23.9698 3.5 24.5191 3.72847 24.976 4.18542C25.433 4.64236 25.6615 5.19167 25.6615 5.83333V22.1667C25.6615 22.8083 25.433 23.3576 24.976 23.8146C24.5191 24.2715 23.9698 24.5 23.3281 24.5H4.66146ZM4.66146 22.1667H23.3281V5.83333H4.66146V22.1667ZM6.99479 19.8333H20.9948V17.5H6.99479V19.8333ZM6.99479 15.1667H11.6615V8.16667H6.99479V15.1667ZM13.9948 15.1667H20.9948V12.8333H13.9948V15.1667ZM13.9948 10.5H20.9948V8.16667H13.9948V10.5Z" fill="white"/></g></g></svg>',
solid_arrow_down: '<svg width="10" height="5" viewBox="0 0 10 5" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5 5L0 0H10L5 5Z" fill="black"/></svg>',
solid_arrow_up: '<svg width="10" height="5" viewBox="0 0 10 5" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5 -4.37114e-07L10 5L0 5L5 -4.37114e-07Z" fill="white"/></svg>',
}, },
} }