Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ class CollectionsApiService {
});
}

searchCollections(query: string) {
return $fetch(`/api/collection`, {
params: {
page: 0,
pageSize: 100,
search: query,
},
});
}

fetchCollectionsQueryFn(
query: () => Record<string, string | number | string[] | undefined>,
): QueryFunction<Pagination<Collection>, readonly unknown[], number> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<!--
Copyright (c) 2025 The Linux Foundation and each contributor.
SPDX-License-Identifier: MIT
-->
<template>
<lfx-dropdown-select
v-model="selectedCollection"
:width="props.width"
:match-width="props.matchWidth"
dropdown-class="max-h-80"
placement="bottom-end"
>
<template #trigger="{ selectedOption }">
<lfx-dropdown-selector
:size="props.size"
:type="props.type"
class="!rounded-full flex items-center justify-center w-full"
>
<div class="flex items-center gap-2">
<lfx-icon
name="rectangle-history"
:size="16"
/>
<span class="text-sm text-neutral-900 truncate">
{{ selectedOption.label || 'All collections' }}
</span>
</div>
</lfx-dropdown-selector>
</template>

<template #default>
<!-- All collections option -->
<lfx-dropdown-item
value="all"
label="All collections"
/>

<lfx-dropdown-separator />

<!-- Search input -->
<lfx-dropdown-search
v-model="searchQuery"
placeholder="Search collections..."
lazy
/>

<lfx-dropdown-separator />

<!-- Collections list -->
<div
v-if="isPending"
class="py-8 flex justify-center"
>
<lfx-spinner />
</div>

<div
v-else-if="!collections.length && searchQuery"
class="py-4 px-3 text-sm text-neutral-500 text-center"
>
No collections found
</div>

<template v-else>
<lfx-dropdown-item
v-for="collection in collections"
:key="collection.id"
:value="collection.slug"
:label="collection.name"
/>
</template>
</template>
</lfx-dropdown-select>
</template>

<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import LfxDropdownSelect from '~/components/uikit/dropdown/dropdown-select.vue';
import LfxDropdownSelector from '~/components/uikit/dropdown/dropdown-selector.vue';
import LfxDropdownItem from '~/components/uikit/dropdown/dropdown-item.vue';
import LfxDropdownSearch from '~/components/uikit/dropdown/dropdown-search.vue';
import LfxDropdownSeparator from '~/components/uikit/dropdown/dropdown-separator.vue';
import LfxIcon from '~/components/uikit/icon/icon.vue';
import type { Collection } from '~~/types/collection';
import type { Pagination } from '~~/types/shared/pagination';
import { COLLECTIONS_API_SERVICE } from '~/components/modules/collection/services/collections.api.service';
import LfxSpinner from '~/components/uikit/spinner/spinner.vue';

const props = withDefaults(
defineProps<{
modelValue?: string;
width?: string;
matchWidth?: boolean;
size?: 'medium' | 'small';
type?: 'transparent' | 'filled';
}>(),
{
modelValue: '',
width: '350px',
matchWidth: false,
size: 'medium',
type: 'transparent',
},
);

const emit = defineEmits<{
(e: 'update:modelValue', value: string): void;
}>();

const selectedCollection = computed({
get: () => props.modelValue,
set: (value: string) => emit('update:modelValue', value),
});

const searchQuery = ref('');
const collections = ref<Collection[]>([]);
const isPending = ref(false);

// Fetch collections from API
const fetchCollections = async () => {
isPending.value = true;
try {
const response = await COLLECTIONS_API_SERVICE.searchCollections(searchQuery.value);
collections.value = (response as Pagination<Collection>).data || [];
} catch (error) {
console.error('Error fetching collections:', error);
collections.value = [];
} finally {
isPending.value = false;
}
};

// Watch for search query changes
watch(
() => searchQuery.value,
() => {
fetchCollections();
},
{ immediate: true },
);
</script>

<script lang="ts">
export default {
name: 'LfxCollectionsFilter',
};
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Copyright (c) 2025 The Linux Foundation and each contributor.
SPDX-License-Identifier: MIT
-->
<template>
<div class="flex flex-col gap-6 items-start w-full pb-6">
<div class="flex flex-col gap-3 md:gap-6 items-start w-full pb-6">
<div
v-if="scrollTop < scrollThreshold"
class="md:hidden block"
Expand All @@ -20,8 +20,12 @@ SPDX-License-Identifier: MIT
</router-link>
</div>
<!-- Icon and Share button -->
<div class="flex justify-between w-full items-start">
<div class="flex transition-all ease-linear flex-col gap-3">

<div class="w-full">
<div
class="flex justify-between w-full items-start"
:class="[scrollTop > scrollThreshold ? '' : 'pb-3']"
>
<div
:class="[scrollTop > scrollThreshold ? 'size-10' : 'size-12']"
class="transition-all ease-linear bg-white border border-neutral-200 rounded-lg flex items-center justify-center"
Expand All @@ -32,30 +36,13 @@ SPDX-License-Identifier: MIT
:size="scrollTop > scrollThreshold ? 20 : 24"
/>
</div>

<div class="flex flex-col gap-1">
<h1
:class="[scrollTop > scrollThreshold ? 'text-2xl ml-13 -mt-12' : 'text-3xl']"
class="transition-all ease-linear font-light font-secondary text-neutral-900 md:block hidden"
>
{{ config?.name }}
</h1>
<!-- mobile navigation -->
<div
class="md:hidden flex justify-start"
:class="[scrollTop > scrollThreshold ? 'ml-13 -mt-12' : '']"
>
<lfx-leaderboard-mobile-nav :leaderboard-key="config.key" />
</div>
</div>
</div>
<div class="mt-1">
<div class="md:block hidden">
<lfx-button
type="tertiary"
type="ghost"
size="small"
button-style="pill"
class="h-9"
:class="[scrollTop > scrollThreshold ? 'invisible' : 'visible']"
@click="handleShare"
>
<lfx-icon
Expand All @@ -68,19 +55,51 @@ SPDX-License-Identifier: MIT
<div class="md:!hidden block">
<lfx-icon-button
icon="share-nodes"
type="transparent"
@click="handleShare"
/>
</div>
</div>
<div
class="hidden justify-between w-full items-center md:flex"
:class="[scrollTop > scrollThreshold ? '' : 'pb-2']"
>
<h1
:class="[scrollTop > scrollThreshold ? 'text-2xl ml-13 -mt-10' : 'text-3xl']"
class="transition-all ease-linear font-light font-secondary text-neutral-900 md:block hidden"
>
{{ config?.name }}
</h1>
<lfx-collections-filter
v-model="selectedCollection"
width="350px"
:size="scrollTop < scrollThreshold ? 'medium' : 'small'"
type="filled"
class="transition-all ease-linear"
:class="[scrollTop > scrollThreshold ? '-mt-10' : '']"
/>
</div>
<div
class="md:hidden flex justify-start transition-all ease-linear"
:class="[scrollTop > scrollThreshold ? 'ml-13 -mt-9 mb-2' : '']"
>
<lfx-leaderboard-mobile-nav :leaderboard-key="config.key" />
</div>
<p
:class="[scrollTop < scrollThreshold ? 'block' : 'hidden']"
class="transition-all ease-linear text-sm text-neutral-500 w-full whitespace-pre-wrap min-h-10"
>
{{ config?.description }}
</p>
</div>

<p
:class="[scrollTop < scrollThreshold ? 'block' : 'hidden']"
class="-mt-5 transition-all ease-linear text-sm text-neutral-500 w-full whitespace-pre-wrap min-h-10"
>
{{ config?.description }}
</p>

<lfx-collections-filter
v-model="selectedCollection"
class="flex md:hidden !w-full"
width="100%"
size="medium"
type="filled"
/>
<div class="relative w-full md:block hidden">
<lfx-leaderboard-search
:config="config"
Expand All @@ -90,7 +109,7 @@ SPDX-License-Identifier: MIT
</div>
<div class="md:hidden block w-full">
<div
class="rounded-full border border-solid border-neutral-200 cursor-pointer flex items-center gap-2 px-3 py-2"
class="rounded-full bg-neutral-50 border border-solid border-neutral-200 cursor-pointer flex items-center gap-2 px-3 py-2"
@click="isSearchOpen = true"
>
<lfx-icon
Expand All @@ -111,7 +130,6 @@ SPDX-License-Identifier: MIT
<div class="p-1 bg-white rounded-lg">
<lfx-leaderboard-search
ref="searchComponentRef"
class="bg-white"
in-modal
:config="config"
@item-click="handleItemClick"
Expand All @@ -124,7 +142,7 @@ SPDX-License-Identifier: MIT
import { ref, watch, nextTick } from 'vue';
import pluralize from 'pluralize';
import type { LeaderboardConfig } from '../../config/types/leaderboard.types';
import LfxLeaderboardMobileNav from './leaderboard-mobile-nav.vue';
import LfxCollectionsFilter from '../filters/collections-filter.vue';
import LfxLeaderboardSearch from './leaderboard-search.vue';
import LfxButton from '~/components/uikit/button/button.vue';
import LfxIconButton from '~/components/uikit/icon-button/icon-button.vue';
Expand All @@ -134,12 +152,15 @@ import { useShareStore } from '~/components/shared/modules/share/store/share.sto
import { LfxRoutes } from '~/components/shared/types/routes';
import LfxModal from '~/components/uikit/modal/modal.vue';
import type { Leaderboard } from '~~/types/leaderboard/leaderboard';
import LfxLeaderboardMobileNav from '~/components/modules/leaderboards/components/sections/leaderboard-mobile-nav.vue';

const { openShareModal } = useShareStore();
const props = defineProps<{
config: LeaderboardConfig;
}>();

const selectedCollection = defineModel<string>('collectionSlug', { default: '' });

const { scrollTop } = useScroll();

const isSearchOpen = ref(false);
Expand Down
Loading