From 498718c975e2a9b849bf6b21de101f4d920dd203 Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos Date: Thu, 24 Sep 2026 12:09:03 -0700 Subject: [PATCH] refactor(resources): share filter panel structure --- .../components/resource-options/index.ts | 1 + .../resource-filter-panel.tsx | 33 ++++++++++++ .../resource-options.test.tsx | 53 ++++++++++++++++++- .../workspace/[workspaceId]/files/files.tsx | 24 ++++----- .../knowledge/[id]/[documentId]/document.tsx | 16 ++++-- .../workspace/[workspaceId]/tables/tables.tsx | 19 +++---- 6 files changed, 119 insertions(+), 27 deletions(-) create mode 100644 apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-filter-panel.tsx diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/index.ts b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/index.ts index 48603fa9448..ab44bb4bf54 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/index.ts @@ -1,3 +1,4 @@ +export { ResourceFilterPanel, ResourceFilterSection } from './resource-filter-panel' export type { ColumnOption, FilterConfig, diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-filter-panel.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-filter-panel.tsx new file mode 100644 index 00000000000..94b2bb0a6bb --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-filter-panel.tsx @@ -0,0 +1,33 @@ +'use client' + +import type { ReactNode } from 'react' +import { FILTER_SECTION_LABEL_CLASS } from '@/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-options' + +interface ResourceFilterPanelProps { + children: ReactNode +} + +/** The filter content frame shared by resource lists and document chunks. */ +export function ResourceFilterPanel({ children }: ResourceFilterPanelProps) { + return
{children}
+} + +interface ResourceFilterSectionProps { + label: string + children: ReactNode + /** A distinct label treatment, such as the document chunk status label. */ + labelClassName?: string +} + +export function ResourceFilterSection({ + label, + children, + labelClassName = FILTER_SECTION_LABEL_CLASS, +}: ResourceFilterSectionProps) { + return ( +
+ {label} + {children} +
+ ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-options.test.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-options.test.tsx index 079c0677838..8a92069fe63 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-options.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-options.test.tsx @@ -4,7 +4,14 @@ import { act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { SortDropdown } from '@/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-options' +import { + ResourceFilterPanel, + ResourceFilterSection, +} from '@/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-filter-panel' +import { + ResourceOptions, + SortDropdown, +} from '@/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-options' const LONG_COLUMN_LABEL = 'highest_current_champion_role_across_the_entire_company' @@ -125,3 +132,47 @@ describe('SortDropdown', () => { expect(onOpenChange).toHaveBeenCalledWith(false) }) }) + +describe('ResourceOptions filter content', () => { + it('keeps section controls focusable and clear actions working in the filter popover', () => { + const onClear = vi.fn() + act(() => { + root.render( + + + + + + + ), + }} + /> + ) + }) + + const trigger = [...document.querySelectorAll('button')].find( + (button) => button.textContent === 'Filter' + ) + expect(trigger).toBeDefined() + act(() => trigger?.click()) + + const choice = [...document.querySelectorAll('button')].find( + (button) => button.textContent === 'Choose status' + ) + expect(choice).toBeDefined() + expect(document.body).toHaveTextContent('Status') + act(() => choice?.focus()) + expect(document.activeElement).toBe(choice) + + const clear = [...document.querySelectorAll('button')].find( + (button) => button.textContent === 'Clear all filters' + ) + act(() => clear?.click()) + expect(onClear).toHaveBeenCalledOnce() + }) +}) diff --git a/apps/sim/app/workspace/[workspaceId]/files/files.tsx b/apps/sim/app/workspace/[workspaceId]/files/files.tsx index 9a05428538a..9cb16f9a489 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/files.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/files.tsx @@ -94,7 +94,10 @@ import type { SearchConfig, SortConfig, } from '@/app/workspace/[workspaceId]/components/resource/components/resource-options' -import { FILTER_SECTION_LABEL_CLASS } from '@/app/workspace/[workspaceId]/components/resource/components/resource-options' +import { + ResourceFilterPanel, + ResourceFilterSection, +} from '@/app/workspace/[workspaceId]/components/resource/components/resource-options' import { timeCell } from '@/app/workspace/[workspaceId]/components/resource/components/time-cell' import { resourceListState } from '@/app/workspace/[workspaceId]/components/resource/is-resource-list-empty' import type { @@ -2004,9 +2007,8 @@ function FilesContent() { : `${uploadedByFilter.length} members` return ( -
-
- File Type + + -
-
- Size + + -
+ {memberOptions.length > 0 && ( -
- Uploaded By + -
+ )} {hasActiveFilters && (
+ ) }, [ typeFilter, diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx index 96cea520580..9de2b6c4cb5 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx @@ -39,6 +39,10 @@ import type { SearchConfig, SortConfig, } from '@/app/workspace/[workspaceId]/components/resource/components/resource-options' +import { + ResourceFilterPanel, + ResourceFilterSection, +} from '@/app/workspace/[workspaceId]/components/resource/components/resource-options' import type { PaginationConfig, ResourceColumn, @@ -737,9 +741,11 @@ export function Document({ const filterContent = useMemo( () => ( -
-
- Status + + -
+ {enabledFilter.length > 0 && (
+ ), [enabledFilter, setEnabledFilter] ) diff --git a/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx b/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx index a9b4257b2fd..199bfd9591d 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx @@ -53,7 +53,10 @@ import type { SearchConfig, SortConfig, } from '@/app/workspace/[workspaceId]/components/resource/components/resource-options' -import { FILTER_SECTION_LABEL_CLASS } from '@/app/workspace/[workspaceId]/components/resource/components/resource-options' +import { + ResourceFilterPanel, + ResourceFilterSection, +} from '@/app/workspace/[workspaceId]/components/resource/components/resource-options' import { timeCell } from '@/app/workspace/[workspaceId]/components/resource/components/time-cell' import { resourceListState } from '@/app/workspace/[workspaceId]/components/resource/is-resource-list-empty' import type { @@ -727,9 +730,8 @@ function TablesContent() { const filterContent = useMemo( () => ( -
-
- Row Count + + -
+ {memberOptions.length > 0 && ( -
- Owner + -
+ )} {hasActiveFilters && (
+ ), [ rowCountFilter,