diff --git a/src/components/remote-virtualized-command/remote-virtualized-command.tsx b/src/components/remote-virtualized-command/remote-virtualized-command.tsx index 0bb45672..5eb077c0 100644 --- a/src/components/remote-virtualized-command/remote-virtualized-command.tsx +++ b/src/components/remote-virtualized-command/remote-virtualized-command.tsx @@ -75,7 +75,7 @@ export const RemoteVirtualizedCommand = ({ const virtualizer = useVirtualizer({ count: filteredValues.length, getScrollElement: () => parentRef.current, - estimateSize: () => 35, + estimateSize: () => 44, }); return ( @@ -104,7 +104,9 @@ export const RemoteVirtualizedCommand = ({ ({ > { const secondItem = screen.getByText('ITEM-B').closest('[cmdk-item]'); expect(firstItem).toHaveStyle({ - height: '35px', transform: 'translateY(0px)', }); expect(secondItem).toHaveStyle({ - height: '35px', transform: 'translateY(35px)', }); }); diff --git a/src/components/remote-virtualized-command/virtualized-items.tsx b/src/components/remote-virtualized-command/virtualized-items.tsx index e289e73c..4da93eab 100644 --- a/src/components/remote-virtualized-command/virtualized-items.tsx +++ b/src/components/remote-virtualized-command/virtualized-items.tsx @@ -4,6 +4,7 @@ import { CommandItem } from '@/components/ui/command'; interface Props { virtualItems: VirtualItem[]; + measureElement?: (element: HTMLDivElement | null) => void; filteredValues: string[]; formatLabel: (value: string) => string; onSelect: (value: string) => void; @@ -11,6 +12,7 @@ interface Props { export const VirtualizedItems = ({ virtualItems, + measureElement, filteredValues, formatLabel, onSelect, @@ -23,10 +25,11 @@ export const VirtualizedItems = ({ return ( ) { +}: React.ComponentProps & { + portalContainer?: HTMLElement | null; +}) { return ( - + parentRef.current, - estimateSize: () => 35, + estimateSize: () => 44, }); const virtualItems = virtualizer.getVirtualItems(); @@ -69,7 +69,13 @@ export const VirtualizedCommand = ({ {!searchValue && beforeItems} No item found. @@ -86,10 +92,11 @@ export const VirtualizedCommand = ({ return ( { ); const selectedOptions = (filterParam?.split(',') || []).map((paramValue) => ({ - label: capitalizeSlug(paramValue), + label: + options.find((option) => option.value === paramValue)?.label ?? + capitalizeSlug(paramValue), value: paramValue, })); diff --git a/src/features/filters/components/active-filters/active-filter-trigger.tsx b/src/features/filters/components/active-filters/active-filter-trigger.tsx index fef1cbf8..2f85dd54 100644 --- a/src/features/filters/components/active-filters/active-filter-trigger.tsx +++ b/src/features/filters/components/active-filters/active-filter-trigger.tsx @@ -32,15 +32,16 @@ export const ActiveFilterTrigger = ({ ...props }: Props) => { const labelText = usePrevious(label, !isPending); + const removeLabel = `Remove ${tooltipLabel ?? label} filter`; return ( - + + + ); diff --git a/src/features/filters/components/more-filters/index.ts b/src/features/filters/components/more-filters/index.ts deleted file mode 100644 index 2e981c28..00000000 --- a/src/features/filters/components/more-filters/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { MoreFilters } from './more-filters'; diff --git a/src/features/filters/components/more-filters/more-filters-item.tsx b/src/features/filters/components/more-filters/more-filters-item.tsx deleted file mode 100644 index 7f4f368b..00000000 --- a/src/features/filters/components/more-filters/more-filters-item.tsx +++ /dev/null @@ -1,51 +0,0 @@ -'use client'; - -import { type TransitionStartFunction } from 'react'; - -import { CommandItem } from '@/components/ui/command'; -import { useFilterQueryState } from '@/features/filters/hooks'; -import { MappedFilterIcon } from '@/features/filters/components/mapped-filter-icon'; - -interface Props { - isPending: boolean; - paramKey: string; - label: string; - defaultValue: string | null; - closeDropdown: () => void; - startTransition: TransitionStartFunction; -} - -export const MoreFiltersItem = (props: Props) => { - const { - isPending, - paramKey, - label, - defaultValue, - closeDropdown, - startTransition, - } = props; - - const [, setFilterParam] = useFilterQueryState(paramKey); - - const onSelect = () => { - closeDropdown(); - startTransition(() => { - setFilterParam(defaultValue); - }); - }; - - return ( - -
-
- -
- {label} -
-
- ); -}; diff --git a/src/features/filters/components/more-filters/more-filters-range-item.tsx b/src/features/filters/components/more-filters/more-filters-range-item.tsx deleted file mode 100644 index c84f507c..00000000 --- a/src/features/filters/components/more-filters/more-filters-range-item.tsx +++ /dev/null @@ -1,79 +0,0 @@ -'use client'; - -import { type TransitionStartFunction } from 'react'; - -import { Button } from '@/components/ui/button'; -import { CommandItem } from '@/components/ui/command'; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from '@/components/ui/popover'; -import { Slider } from '@/components/ui/slider'; -import { MappedFilterIcon } from '@/features/filters/components/mapped-filter-icon'; -import type { RangeFilterConfig } from '@/features/filters/schemas'; - -import { useMoreFiltersRangeItem } from './use-more-filters-range-item'; - -interface Props { - isPending: boolean; - config: RangeFilterConfig; - closeDropdown: () => void; - startTransition: TransitionStartFunction; -} - -export const MoreFiltersRangeItem = (props: Props) => { - const { isPending, config, closeDropdown, startTransition } = props; - - const { - open, - setOpen, - value, - setValue, - formattedLow, - formattedHigh, - canApply, - handleApply, - } = useMoreFiltersRangeItem({ config, closeDropdown, startTransition }); - - return ( - - - setOpen(true)} - > -
-
- -
- {config.label} -
-
-
- e.preventDefault()} - > -
-
- {formattedLow} - {formattedHigh} -
- setValue(v as [number, number])} - /> - -
-
-
- ); -}; diff --git a/src/features/filters/components/more-filters/more-filters.test.tsx b/src/features/filters/components/more-filters/more-filters.test.tsx deleted file mode 100644 index 74ed85f8..00000000 --- a/src/features/filters/components/more-filters/more-filters.test.tsx +++ /dev/null @@ -1,156 +0,0 @@ -// @vitest-environment jsdom -import { cleanup, render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; - -// Mock ResizeObserver for cmdk component -class ResizeObserverMock { - observe() {} - unobserve() {} - disconnect() {} -} -global.ResizeObserver = ResizeObserverMock; - -// Mock scrollIntoView for cmdk component -Element.prototype.scrollIntoView = vi.fn(); - -// Define FILTER_KIND locally to avoid importing from constants (which has env deps) -const FILTER_KIND = { - SORT: 'SORT', - SWITCH: 'SWITCH', - RADIO: 'RADIO', - CHECKBOX: 'CHECKBOX', - SEARCH: 'SEARCH', - REMOTE_SEARCH: 'REMOTE_SEARCH', - RANGE: 'RANGE', -} as const; - -import { type FilterConfigSchema } from '@/features/filters/schemas'; - -// Mock the constants module to avoid clientEnv validation -vi.mock('@/features/filters/constants', () => ({ - FILTER_KIND: { - SORT: 'SORT', - SWITCH: 'SWITCH', - RADIO: 'RADIO', - CHECKBOX: 'CHECKBOX', - SEARCH: 'SEARCH', - REMOTE_SEARCH: 'REMOTE_SEARCH', - RANGE: 'RANGE', - }, - REMOTE_FILTERS_SET: new Set(['tags']), -})); - -// Mock next/navigation -const mockSearchParams = new URLSearchParams(); -vi.mock('next/navigation', () => ({ - useSearchParams: () => mockSearchParams, -})); - -// Mock nuqs -const mockSetFilterParam = vi.fn(); -vi.mock('nuqs', () => ({ - useQueryState: () => [null, mockSetFilterParam], -})); - -// Mock @bprogress/next -vi.mock('@bprogress/next', () => ({ - useProgress: () => ({ start: vi.fn() }), -})); - -import { MoreFilters } from './more-filters'; - -afterEach(() => { - cleanup(); -}); - -describe('MoreFilters', () => { - const configs: FilterConfigSchema[] = [ - { - kind: FILTER_KIND.SWITCH, - label: 'Remote Only', - paramKey: 'remote', - position: 1, - analytics: { id: null, name: null }, - }, - { - kind: FILTER_KIND.CHECKBOX, - label: 'Seniority', - paramKey: 'seniority', - position: 2, - analytics: { id: null, name: null }, - options: [{ label: 'Senior', value: 'senior' }], - }, - ]; - - beforeEach(() => { - vi.clearAllMocks(); - mockSearchParams.forEach((_, key) => mockSearchParams.delete(key)); - }); - - it('renders "More Filters" button', () => { - render(); - - expect( - screen.getByRole('button', { name: /more filters/i }), - ).toBeInTheDocument(); - }); - - it('opens popover on button click', async () => { - const user = userEvent.setup(); - render(); - - await user.click(screen.getByRole('button', { name: /more filters/i })); - - expect(screen.getByPlaceholderText(/search filters/i)).toBeInTheDocument(); - }); - - it('displays filter options from useMoreFiltersOptions', async () => { - const user = userEvent.setup(); - render(); - - await user.click(screen.getByRole('button', { name: /more filters/i })); - - expect(screen.getByText('Remote Only')).toBeInTheDocument(); - expect(screen.getByText('Seniority')).toBeInTheDocument(); - }); - - it('groups filters under meaningful headings', async () => { - const user = userEvent.setup(); - render(); - - await user.click(screen.getByRole('button', { name: /more filters/i })); - - expect(screen.getByText('Role & requirements')).toBeInTheDocument(); - expect(screen.getByText('More options')).toBeInTheDocument(); - }); - - it('keeps the scrollable menu inside the mobile filter sheet', async () => { - const user = userEvent.setup(); - const { container } = render( -
- -
, - ); - - await user.click(screen.getByRole('button', { name: /more filters/i })); - - const sheet = container.querySelector('[data-slot="sheet-content"]'); - const menu = sheet?.querySelector('[data-slot="popover-content"]'); - const list = menu?.querySelector('[data-slot="command-list"]'); - - expect(menu).toBeInTheDocument(); - expect(list).toHaveClass('touch-pan-y', 'overscroll-contain'); - }); - - it('excludes active filters from options', async () => { - mockSearchParams.set('remote', 'true'); - const user = userEvent.setup(); - render(); - - await user.click(screen.getByRole('button', { name: /more filters/i })); - - expect(screen.queryByText('Remote Only')).not.toBeInTheDocument(); - expect(screen.getByText('Seniority')).toBeInTheDocument(); - }); -}); diff --git a/src/features/filters/components/more-filters/more-filters.tsx b/src/features/filters/components/more-filters/more-filters.tsx deleted file mode 100644 index 91674d95..00000000 --- a/src/features/filters/components/more-filters/more-filters.tsx +++ /dev/null @@ -1,129 +0,0 @@ -'use client'; - -import { useCallback, useState, useTransition } from 'react'; -import { ListFilterPlusIcon, LoaderIcon } from 'lucide-react'; - -import { - Popover, - PopoverContent, - PopoverTrigger, -} from '@/components/ui/popover'; - -import { Button } from '@/components/ui/button'; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandList, -} from '@/components/ui/command'; -import { FILTER_KIND } from '@/features/filters/constants'; -import { groupFilterConfigs } from '@/features/filters/filter-groups'; -import { type FilterConfigSchema } from '@/features/filters/schemas'; - -import { MoreFiltersItem } from './more-filters-item'; -import { MoreFiltersRangeItem } from './more-filters-range-item'; -import { useMoreFiltersOptions } from './use-more-filters-options'; - -interface Props { - configs: FilterConfigSchema[]; -} - -export const MoreFilters = ({ configs }: Props) => { - const options = useMoreFiltersOptions(configs); - const groups = groupFilterConfigs(options); - const [open, setOpen] = useState(false); - const [portalContainer, setPortalContainer] = useState( - null, - ); - const setContainerRef = useCallback((node: HTMLDivElement | null) => { - const sheet = - node?.closest('[data-slot="sheet-content"]') ?? null; - setPortalContainer((current) => (current === sheet ? current : sheet)); - }, []); - const closeDropdown = () => setOpen(false); - const [isPending, startTransition] = useTransition(); - return ( -
- - - - - - - - - No results found. - {groups.map((group) => ( - - {group.configs.map((config) => { - if (config.kind === FILTER_KIND.RANGE) { - return ( - - ); - } - - return ( - - ); - })} - - ))} - - - - -
- ); -}; - -const getDefaultValue = (config: FilterConfigSchema): string | null => { - switch (config.kind) { - case FILTER_KIND.SWITCH: - return 'true'; - case FILTER_KIND.RADIO: - case FILTER_KIND.CHECKBOX: - case FILTER_KIND.SEARCH: - case FILTER_KIND.REMOTE_SEARCH: - return config.options[0].value; - default: - return null; - } -}; diff --git a/src/features/filters/components/more-filters/use-more-filters-options.test.ts b/src/features/filters/components/more-filters/use-more-filters-options.test.ts deleted file mode 100644 index 82195025..00000000 --- a/src/features/filters/components/more-filters/use-more-filters-options.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -// @vitest-environment jsdom -import { renderHook } from '@testing-library/react'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; - -// Define FILTER_KIND locally to avoid importing from constants (which has env deps) -const FILTER_KIND = { - SORT: 'SORT', - SWITCH: 'SWITCH', - RADIO: 'RADIO', - CHECKBOX: 'CHECKBOX', - SEARCH: 'SEARCH', - REMOTE_SEARCH: 'REMOTE_SEARCH', -} as const; - -import { type FilterConfigSchema } from '@/features/filters/schemas'; - -// Mock the constants module to avoid clientEnv validation -vi.mock('@/features/filters/constants', () => ({ - FILTER_KIND: { - SORT: 'SORT', - SWITCH: 'SWITCH', - RADIO: 'RADIO', - CHECKBOX: 'CHECKBOX', - SEARCH: 'SEARCH', - REMOTE_SEARCH: 'REMOTE_SEARCH', - }, - REMOTE_FILTERS_SET: new Set(['tags']), -})); - -// Mock next/navigation -const mockSearchParams = new URLSearchParams(); -vi.mock('next/navigation', () => ({ - useSearchParams: () => mockSearchParams, -})); - -import { useMoreFiltersOptions } from './use-more-filters-options'; - -const createSwitchConfig = ( - label: string, - paramKey: string, -): FilterConfigSchema => ({ - kind: FILTER_KIND.SWITCH, - label, - paramKey, - position: 1, - analytics: { id: null, name: null }, -}); - -const createCheckboxConfig = ( - label: string, - paramKey: string, -): FilterConfigSchema => ({ - kind: FILTER_KIND.CHECKBOX, - label, - paramKey, - position: 1, - analytics: { id: null, name: null }, - options: [{ label: 'Option 1', value: 'opt1' }], -}); - -const createSortConfig = ( - label: string, - paramKey: string, -): FilterConfigSchema => ({ - kind: FILTER_KIND.SORT, - label, - paramKey, - position: 1, - analytics: { id: null, name: null }, - options: [{ label: 'Date', value: 'date' }], -}); - -describe('useMoreFiltersOptions', () => { - beforeEach(() => { - // Clear search params before each test - mockSearchParams.forEach((_, key) => mockSearchParams.delete(key)); - }); - - it('returns all non-order configs when no filters are active', () => { - const configs: FilterConfigSchema[] = [ - createSwitchConfig('Remote', 'remote'), - createCheckboxConfig('Seniority', 'seniority'), - ]; - - const { result } = renderHook(() => useMoreFiltersOptions(configs)); - - expect(result.current).toHaveLength(2); - expect(result.current.map((c) => c.label)).toEqual(['Remote', 'Seniority']); - }); - - it('excludes configs that are currently active', () => { - mockSearchParams.set('remote', 'true'); - - const configs: FilterConfigSchema[] = [ - createSwitchConfig('Remote', 'remote'), - createCheckboxConfig('Seniority', 'seniority'), - ]; - - const { result } = renderHook(() => useMoreFiltersOptions(configs)); - - expect(result.current).toHaveLength(1); - expect(result.current[0].label).toBe('Seniority'); - }); - - it('excludes configs with "order" in label (case-insensitive)', () => { - const configs: FilterConfigSchema[] = [ - createSwitchConfig('Remote', 'remote'), - createSortConfig('Order By', 'orderBy'), - createSortConfig('Sort Order', 'sortOrder'), - ]; - - const { result } = renderHook(() => useMoreFiltersOptions(configs)); - - expect(result.current).toHaveLength(1); - expect(result.current[0].label).toBe('Remote'); - }); - - it('returns empty array when all configs are active or orders', () => { - mockSearchParams.set('remote', 'true'); - - const configs: FilterConfigSchema[] = [ - createSwitchConfig('Remote', 'remote'), - createSortConfig('Order By', 'orderBy'), - ]; - - const { result } = renderHook(() => useMoreFiltersOptions(configs)); - - expect(result.current).toHaveLength(0); - }); -}); diff --git a/src/features/filters/components/more-filters/use-more-filters-options.ts b/src/features/filters/components/more-filters/use-more-filters-options.ts deleted file mode 100644 index 8f8d8dbd..00000000 --- a/src/features/filters/components/more-filters/use-more-filters-options.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { type FilterConfigSchema } from '@/features/filters/schemas'; -import { useActiveFilterLabels } from '@/features/filters/hooks'; - -export const useMoreFiltersOptions = ( - configs: FilterConfigSchema[], -): FilterConfigSchema[] => { - const activeLabels = useActiveFilterLabels(configs); - return configs.filter((config) => { - const isActive = activeLabels.has(config.label); - const isOrder = config.label.toLowerCase().includes('order'); - return !isActive && !isOrder; - }); -}; diff --git a/src/features/filters/components/more-filters/use-more-filters-range-item.ts b/src/features/filters/components/more-filters/use-more-filters-range-item.ts deleted file mode 100644 index 6c26f54b..00000000 --- a/src/features/filters/components/more-filters/use-more-filters-range-item.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { useState, type TransitionStartFunction } from 'react'; - -import { useProgress } from '@bprogress/next'; -import { useQueryState } from 'nuqs'; - -import type { RangeFilterConfig } from '@/features/filters/schemas'; -import { usePillarFilterMode } from '@/features/filters/hooks/use-pillar-filter-mode'; -import { - calculateSliderStep, - formatRangeValue, - roundToNiceNumber, -} from '@/features/filters/utils'; - -interface Props { - config: RangeFilterConfig; - closeDropdown: () => void; - startTransition: TransitionStartFunction; -} - -export const useMoreFiltersRangeItem = ({ - config, - closeDropdown, - startTransition, -}: Props) => { - const { start } = useProgress(); - const pillarMode = usePillarFilterMode(); - - const [open, setOpen] = useState(false); - const [value, setValue] = useState<[number, number]>([ - config.lowest.value, - config.highest.value, - ]); - - const [, setLowParam] = useQueryState(config.lowest.paramKey); - const [, setHighParam] = useQueryState(config.highest.paramKey); - const [, setPage] = useQueryState('page'); - - const step = calculateSliderStep(config.lowest.value, config.highest.value); - const roundedLow = roundToNiceNumber(value[0], step); - const roundedHigh = roundToNiceNumber(value[1], step); - - const roundedDefaultLow = roundToNiceNumber(config.lowest.value, step); - const roundedDefaultHigh = roundToNiceNumber(config.highest.value, step); - - const formattedLow = formatRangeValue(roundedLow, config.prefix); - const formattedHigh = formatRangeValue(roundedHigh, config.prefix); - - // Valid range: min must be less than max - const isValidRange = roundedLow < roundedHigh; - - // Full range: selecting the entire range is essentially "no filter" - const isFullRange = - roundedLow === roundedDefaultLow && roundedHigh === roundedDefaultHigh; - - const canApply = isValidRange && !isFullRange; - - const handleApply = () => { - if (!canApply) return; - start(); - setOpen(false); - closeDropdown(); - - if (pillarMode) { - pillarMode.navigate({ - [config.lowest.paramKey]: String(roundedLow), - [config.highest.paramKey]: String(roundedHigh), - }); - return; - } - - startTransition(() => { - setPage(null); - setLowParam(String(roundedLow)); - setHighParam(String(roundedHigh)); - }); - }; - - return { - open, - setOpen, - value, - setValue, - formattedLow, - formattedHigh, - canApply, - handleApply, - }; -}; diff --git a/src/features/filters/components/suggested-filters/suggested-filter-range.tsx b/src/features/filters/components/suggested-filters/suggested-filter-range.tsx index d8ea6df8..665d3432 100644 --- a/src/features/filters/components/suggested-filters/suggested-filter-range.tsx +++ b/src/features/filters/components/suggested-filters/suggested-filter-range.tsx @@ -8,7 +8,7 @@ import { Popover, PopoverContent, PopoverTrigger, -} from '@/components/ui/popover'; +} from '@/features/filters/components/filter-overlay'; import { Slider } from '@/components/ui/slider'; import { useRangeFilterState } from '@/features/filters/hooks/use-range-filter-state'; import { MappedFilterIcon } from '@/features/filters/components/mapped-filter-icon'; diff --git a/src/features/filters/components/suggested-filters/suggested-filter-remote-search.tsx b/src/features/filters/components/suggested-filters/suggested-filter-remote-search.tsx index 8581af40..a1f1d7dc 100644 --- a/src/features/filters/components/suggested-filters/suggested-filter-remote-search.tsx +++ b/src/features/filters/components/suggested-filters/suggested-filter-remote-search.tsx @@ -9,7 +9,7 @@ import { Popover, PopoverContent, PopoverTrigger, -} from '@/components/ui/popover'; +} from '@/features/filters/components/filter-overlay'; import { RemoteVirtualizedCommand } from '@/components/remote-virtualized-command'; import { FILTER_POPOVER_CONTENT_CLASS } from '@/features/filters/constants'; import { getRemoteFilterEndpoint } from '@/features/filters/utils'; diff --git a/src/features/filters/components/suggested-filters/suggested-filter-search.tsx b/src/features/filters/components/suggested-filters/suggested-filter-search.tsx index 5c44c20a..41efe8be 100644 --- a/src/features/filters/components/suggested-filters/suggested-filter-search.tsx +++ b/src/features/filters/components/suggested-filters/suggested-filter-search.tsx @@ -8,7 +8,7 @@ import { Popover, PopoverContent, PopoverTrigger, -} from '@/components/ui/popover'; +} from '@/features/filters/components/filter-overlay'; import { VirtualizedCommand } from '@/components/virtualized-command'; import { FILTER_POPOVER_CONTENT_CLASS } from '@/features/filters/constants'; import { useFilterQueryState } from '@/features/filters/hooks'; diff --git a/src/features/filters/components/suggested-filters/suggested-filter-select.tsx b/src/features/filters/components/suggested-filters/suggested-filter-select.tsx index 858dfcfa..ef0c5b8b 100644 --- a/src/features/filters/components/suggested-filters/suggested-filter-select.tsx +++ b/src/features/filters/components/suggested-filters/suggested-filter-select.tsx @@ -9,7 +9,7 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; +} from '@/features/filters/components/filter-overlay'; import { FILTER_DROPDOWN_CONTENT_CLASS } from '@/features/filters/constants'; import { useFilterQueryState } from '@/features/filters/hooks'; import { MappedFilterIcon } from '@/features/filters/components/mapped-filter-icon'; diff --git a/src/features/filters/components/suggested-filters/suggested-filter-trigger.tsx b/src/features/filters/components/suggested-filters/suggested-filter-trigger.tsx index 92bbd15a..bdf4e72b 100644 --- a/src/features/filters/components/suggested-filters/suggested-filter-trigger.tsx +++ b/src/features/filters/components/suggested-filters/suggested-filter-trigger.tsx @@ -16,11 +16,11 @@ export const SuggestedFilterTrigger = ({ ); diff --git a/src/features/filters/components/suggested-filters/use-suggested-filters.ts b/src/features/filters/components/suggested-filters/use-suggested-filters.ts index 8f5b6bb1..5583fa18 100644 --- a/src/features/filters/components/suggested-filters/use-suggested-filters.ts +++ b/src/features/filters/components/suggested-filters/use-suggested-filters.ts @@ -1,7 +1,6 @@ import { useSearchParams } from 'next/navigation'; import { FILTER_KIND } from '@/features/filters/constants'; -import { isPrimaryFilter } from '@/features/filters/filter-groups'; import { useActiveFilterLabels } from '@/features/filters/hooks'; import { usePillarFilterMode } from '@/features/filters/hooks/use-pillar-filter-mode'; import { type FilterConfigSchema } from '@/features/filters/schemas'; @@ -14,7 +13,7 @@ export const useSuggestedFilters = ( const activeLabels = useActiveFilterLabels(configs); return configs.filter((config) => { - if (!isPrimaryFilter(config)) return false; + if (config.kind === FILTER_KIND.SORT) return false; // For range filters, check if NEITHER param is set. Ranges are never // mock-active in pillar mode, and stray query params on a static pillar diff --git a/src/features/filters/constants.ts b/src/features/filters/constants.ts index 90ad9ff2..5b21de8f 100644 --- a/src/features/filters/constants.ts +++ b/src/features/filters/constants.ts @@ -30,6 +30,6 @@ export const REMOTE_FILTERS = { export const REMOTE_FILTERS_SET = new Set(Object.keys(REMOTE_FILTERS)); export const FILTER_POPOVER_CONTENT_CLASS = - 'relative flex w-fit max-w-60 min-w-32 flex-col gap-2 border-neutral-800 p-0'; + 'relative flex w-80 flex-col border-neutral-800 p-0'; -export const FILTER_DROPDOWN_CONTENT_CLASS = 'w-fit max-w-60 min-w-32'; +export const FILTER_DROPDOWN_CONTENT_CLASS = 'w-72'; diff --git a/src/features/filters/filter-groups.test.ts b/src/features/filters/filter-groups.test.ts index f6e4ecb1..d8855b34 100644 --- a/src/features/filters/filter-groups.test.ts +++ b/src/features/filters/filter-groups.test.ts @@ -14,11 +14,7 @@ vi.mock('@/features/filters/constants', () => ({ import type { FilterConfigSchema } from '@/features/filters/schemas'; -import { - getFilterGroup, - groupFilterConfigs, - isPrimaryFilter, -} from './filter-groups'; +import { getFilterGroup, groupFilterConfigs } from './filter-groups'; const shared = { position: 1, @@ -41,31 +37,6 @@ const switchFilter = (label: string, paramKey: string): FilterConfigSchema => ({ }); describe('filter groups', () => { - it('keeps the most-used role, work, location, pay, and timing filters primary', () => { - const primary = [ - checkbox('Category', 'classifications'), - checkbox('Commitment', 'commitments'), - checkbox('Seniority', 'seniority'), - checkbox('Skills', 'tags'), - checkbox('Work Mode', 'workModes'), - checkbox('Country', 'countries'), - checkbox('Publication Date', 'publicationDate'), - checkbox('New Active Leads', 'newActiveLeads'), - { - ...shared, - kind: 'RANGE', - label: 'Salary', - lowest: { paramKey: 'minSalaryRange', value: 0 }, - highest: { paramKey: 'maxSalaryRange', value: 500_000 }, - prefix: '$', - } satisfies FilterConfigSchema, - ]; - - expect(primary.every(isPrimaryFilter)).toBe(true); - expect(isPrimaryFilter(checkbox('Investors', 'investors'))).toBe(false); - expect(isPrimaryFilter(switchFilter('Has Token', 'token'))).toBe(false); - }); - it('groups every filter once in a stable, meaningful order', () => { const configs = [ checkbox('Investors', 'investors'), diff --git a/src/features/filters/filter-groups.ts b/src/features/filters/filter-groups.ts index b464570c..9580b787 100644 --- a/src/features/filters/filter-groups.ts +++ b/src/features/filters/filter-groups.ts @@ -102,26 +102,6 @@ export interface FilterConfigGroup { configs: FilterConfigSchema[]; } -const PRIMARY_FILTER_PARAM_KEYS = new Set([ - 'classifications', - 'commitments', - 'seniority', - 'tags', - 'workModes', - 'countries', - 'publicationDate', - 'minSalaryRange', - 'maxSalaryRange', - 'minCurrentMaintainers', - 'maxCurrentMaintainers', - 'minActiveLeads', - 'maxActiveLeads', - 'newActiveLeads', - 'steppedDownLeads', - 'movedLeads', - 'earlyLeadDepartures', -]); - export const getFilterParamKeys = (config: FilterConfigSchema): string[] => { if (config.kind === FILTER_KIND.RANGE) { return [config.lowest.paramKey, config.highest.paramKey]; @@ -130,11 +110,6 @@ export const getFilterParamKeys = (config: FilterConfigSchema): string[] => { return [config.paramKey]; }; -export const isPrimaryFilter = (config: FilterConfigSchema): boolean => - getFilterParamKeys(config).some((paramKey) => - PRIMARY_FILTER_PARAM_KEYS.has(paramKey), - ); - export const getFilterGroup = ( config: FilterConfigSchema, ): { id: FilterGroupId; label: string } => {