feat: marketplace sort

This commit is contained in:
StyleZhang 2024-10-29 18:13:47 +08:00
parent 7e378e219c
commit 62fa90e30e
7 changed files with 121 additions and 3 deletions

View File

@ -0,0 +1,4 @@
export const DEFAULT_SORT = {
sortBy: 'install_count',
sortOrder: 'DESC',
}

View File

@ -12,8 +12,12 @@ import {
import { useDebounceFn } from 'ahooks'
import { PLUGIN_TYPE_SEARCH_MAP } from './plugin-type-switch'
import type { Plugin } from '../types'
import type { PluginsSearchParams } from './types'
import type {
PluginsSearchParams,
PluginsSort,
} from './types'
import { getMarketplacePlugins } from './utils'
import { DEFAULT_SORT } from './constants'
export type MarketplaceContextValue = {
intersected: boolean
@ -26,6 +30,8 @@ export type MarketplaceContextValue = {
handleActivePluginTypeChange: (type: string) => void
plugins?: Plugin[]
setPlugins?: (plugins: Plugin[]) => void
sort: PluginsSort
handleSortChange: (sort: PluginsSort) => void
}
export const MarketplaceContext = createContext<MarketplaceContextValue>({
@ -39,6 +45,8 @@ export const MarketplaceContext = createContext<MarketplaceContextValue>({
handleActivePluginTypeChange: () => {},
plugins: undefined,
setPlugins: () => {},
sort: DEFAULT_SORT,
handleSortChange: () => {},
})
type MarketplaceContextProviderProps = {
@ -57,6 +65,7 @@ export const MarketplaceContextProvider = ({
const [filterPluginTags, setFilterPluginTags] = useState<string[]>([])
const [activePluginType, setActivePluginType] = useState(PLUGIN_TYPE_SEARCH_MAP.all)
const [plugins, setPlugins] = useState<Plugin[]>()
const [sort, setSort] = useState(DEFAULT_SORT)
const handleUpdatePlugins = useCallback(async (query: PluginsSearchParams) => {
const { marketplacePlugins } = await getMarketplacePlugins(query)
@ -82,6 +91,10 @@ export const MarketplaceContextProvider = ({
setActivePluginType(type)
}, [])
const handleSortChange = useCallback((sort: PluginsSort) => {
setSort(sort)
}, [])
return (
<MarketplaceContext.Provider
value={{
@ -95,6 +108,8 @@ export const MarketplaceContextProvider = ({
handleActivePluginTypeChange,
plugins,
setPlugins,
sort,
handleSortChange,
}}
>
{children}

View File

@ -3,6 +3,7 @@ import type { Plugin } from '../../types'
import type { MarketplaceCollection } from '../types'
import { useMarketplaceContext } from '../context'
import List from './index'
import SortDropdown from '../sort-dropdown'
type ListWrapperProps = {
marketplaceCollections: MarketplaceCollection[]
@ -16,6 +17,11 @@ const ListWrapper = ({
return (
<div className='px-12 py-2 bg-background-default-subtle'>
<div className='flex items-center'>
<div className='title-xl-semi-bold text-text-primary'>134 results</div>
<div className='mx-3 w-[1px] h-3.5 bg-divider-regular'></div>
<SortDropdown />
</div>
<List
marketplaceCollections={marketplaceCollections}
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}

View File

@ -13,7 +13,7 @@ const SearchBox = () => {
return (
<div
className={cn(
'sticky top-3 flex items-center m-auto p-1.5 w-[640px] h-11 border border-components-chat-input-border bg-components-panel-bg-blur rounded-xl shadow-md z-[11]',
'sticky top-3 flex items-center mx-auto p-1.5 w-[640px] h-11 border border-components-chat-input-border bg-components-panel-bg-blur rounded-xl shadow-md z-[11]',
!intersected && 'w-[508px] transition-[width] duration-300',
)}
>

View File

@ -0,0 +1,88 @@
'use client'
import { useState } from 'react'
import {
RiArrowDownSLine,
RiCheckLine,
} from '@remixicon/react'
import { useMarketplaceContext } from '../context'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
const options = [
{
value: 'install_count',
order: 'DESC',
text: 'Most Popular',
},
{
value: 'version_updated_at',
order: 'DESC',
text: 'Recently Updated',
},
{
value: 'created_at',
order: 'DESC',
text: 'Newly Released',
},
{
value: 'created_at',
order: 'ASC',
text: 'First Released',
},
]
const SortDropdown = () => {
const sort = useMarketplaceContext(v => v.sort)
const handleSortChange = useMarketplaceContext(v => v.handleSortChange)
const [open, setOpen] = useState(false)
const selectedOption = options.find(option => option.value === sort.sortBy && option.order === sort.sortOrder)!
return (
<PortalToFollowElem
placement='bottom-start'
offset={{
mainAxis: 4,
crossAxis: 0,
}}
open={open}
onOpenChange={setOpen}
>
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
<div className='flex items-center px-2 pr-3 h-8 rounded-lg bg-state-base-hover-alt cursor-pointer'>
<span className='mr-1 system-sm-regular'>
Sort by
</span>
<span className='mr-1 system-sm-medium'>
{selectedOption.text}
</span>
<RiArrowDownSLine className='w-4 h-4 text-text-tertiary' />
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<div className='p-1 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
{
options.map(option => (
<div
key={`${option.value}-${option.order}`}
className='flex items-center justify-between px-3 pr-2 h-8 cursor-pointer system-md-regular'
onClick={() => handleSortChange({ sortBy: option.value, sortOrder: option.order })}
>
{option.text}
{
sort.sortBy === option.value && sort.sortOrder === option.order && (
<RiCheckLine className='ml-2 w-4 h-4 text-text-accent' />
)
}
</div>
))
}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default SortDropdown

View File

@ -27,3 +27,8 @@ export type PluginsSearchParams = {
category?: string
tag?: string
}
export type PluginsSort = {
sortBy: string
sortOrder: string
}

View File

@ -36,7 +36,7 @@ export const getMarketplacePlugins = async (query: PluginsSearchParams) => {
let marketplacePlugins = [] as Plugin[]
try {
const marketplacePluginsData = await globalThis.fetch(
`${MARKETPLACE_API_PREFIX}/plugins`,
`${MARKETPLACE_API_PREFIX}/plugins/search/basic`,
{
method: 'POST',
headers: {