[WEB-4969] feat: add toggle button for work item filters row visibility#7865
[WEB-4969] feat: add toggle button for work item filters row visibility#7865
Conversation
|
Linked to Plane Work Item(s) This comment was auto-generated by Plane |
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds a work-item filters toggle component across headers, introduces filter visibility controls and showOnMount plumbing, updates types and store APIs for work-item filter instances, adjusts import paths, and refactors rich-filters UI variants and button config types. Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant H as Header UI
participant W as WorkItemFiltersToggle
participant S as WorkItemFilters Store
participant F as IWorkItemFilterInstance
participant FH as FilterInstanceHelper
U->>H: Open page / header mounts
H->>W: render(entityType, entityId)
W->>S: getFilter(entityType, entityId)
alt existing filter
S-->>W: IWorkItemFilterInstance (existing)
else create
S->>F: getOrCreateFilter({ showOnMount? })
F->>FH: setInitialVisibility(options)
FH-->>F: isVisible initialized
S-->>W: IWorkItemFilterInstance (new)
end
W-->>H: display toggle (F.isVisible)
U->>W: click toggle
W->>F: toggleVisibility()
F->>FH: toggleVisibility()
FH-->>F: isVisible updated
F-->>W: new isVisible
W-->>H: update UI (show/hide filters row)
sequenceDiagram
participant C as FiltersToggle (component)
participant F as IWorkItemFilterInstance
participant U as User
C->>F: read hasActiveFilters, hasUpdates, isVisible
alt no conditions & hidden
C-->>U: render AddFilterButton
else
C-->>U: render toggle + changes pill
end
U->>C: click toggle
C->>F: toggleVisibility(optional)
F->>F: update isVisible (via helper)
F-->>C: new isVisible
C-->>U: update rendered state
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧬 Code graph analysis (1)packages/shared-state/src/store/work-item-filters/filter.store.ts (3)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
🔇 Additional comments (7)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🧪 Early access (Sonnet 4.5): enabledWe are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience. Note:
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/shared-state/src/store/rich-filters/filter-helpers.ts (1)
61-63: Fix JSDoc: wrong @template, outdated @param, incorrect @returns
- Line 61:
@template Kshould be@template P.- Lines 74-79: constructor docs still mention
@param adapter; update to@param filterInstanceand@param params.- Lines 113-117: method doesn’t return a value; remove the
@returnsline or return a boolean.- * @template K - The filter property type extending TFilterProperty + * @template P - The filter property type extending TFilterProperty @@ - * @param adapter - The filter adapter for converting between internal and external formats + * @param filterInstance - The parent filter instance + * @param params - Additional parameters (e.g., adapter for conversions) @@ - * @returns The initial visibility stateAlso applies to: 74-79, 113-117
apps/web/core/components/rich-filters/add-filters-button.tsx (1)
10-21: Add missinglabelin inline buttonConfig for AddFilterButton
In apps/web/core/components/rich-filters/filters-toggle.tsx (lines 38–41), the inline buttonConfig object omits the now-requiredlabel: string | null. Addlabel: null(or an appropriate string) to satisfy the updated prop type. The filters-row.tsx usage already includeslabel: null.
🧹 Nitpick comments (11)
packages/shared-state/src/store/rich-filters/filter-helpers.ts (4)
84-88: MobX: don’t double-decorate actionsMethods are wrapped with
action(...)and also annotated asactioninmakeObservable. Pick one. Keeping themakeObservableannotations and removing the wrappers is cleaner.- makeObservable(this, { - isVisible: observable, - setInitialVisibility: action, - toggleVisibility: action, - }); + makeObservable(this, { + isVisible: observable, + setInitialVisibility: action, + toggleVisibility: action, + }); @@ - setInitialVisibility: IFilterInstanceHelper<P, E>["setInitialVisibility"] = action((visibilityOption) => { + setInitialVisibility: IFilterInstanceHelper<P, E>["setInitialVisibility"] = (visibilityOption) => { @@ - }); + }; @@ - toggleVisibility: IFilterInstanceHelper<P, E>["toggleVisibility"] = action((isVisible) => { + toggleVisibility: IFilterInstanceHelper<P, E>["toggleVisibility"] = (isVisible) => { @@ - }); + };Also applies to: 118-147
98-101: Remove redundant deep clone (toJS already clones)
toJSreturns a deep-cloned snapshot; combining it withcloneDeepis extra work.- return this.adapter.toInternal(toJS(cloneDeep(initialExpression))); + return this.adapter.toInternal(toJS(initialExpression));
20-22: Single source of truth for adapterYou already receive
filterInstance; it exposesadapter. Passing a secondadapterviaparamsrisks divergence.-type TFilterInstanceHelperParams<P extends TFilterProperty, E extends TExternalFilter> = { - adapter: IFilterAdapter<P, E>; -}; +type TFilterInstanceHelperParams<P extends TFilterProperty, E extends TExternalFilter> = Record<string, never>; // reserved for future @@ - private adapter: IFilterAdapter<P, E>; + private adapter: IFilterAdapter<P, E>; @@ - constructor(filterInstance: IFilterInstance<P, E>, params: TFilterInstanceHelperParams<P, E>) { + constructor(filterInstance: IFilterInstance<P, E>, _params: TFilterInstanceHelperParams<P, E>) { this._filterInstance = filterInstance; - this.adapter = params.adapter; + this.adapter = filterInstance.adapter;If changing the signature is too invasive for this PR, at least assert equality in dev builds to catch mismatches.
Also applies to: 70-83
222-228: Optional: route unsupported operators to a no-op helper instead of console.warnLibrary logs can get noisy. Consider a no-op branch or using your shared logger (with debug level) to avoid polluting console in production.
apps/web/core/components/rich-filters/add-filters-button.tsx (3)
104-107: Icon-only state lacks an accessible nameWhen
labelisnull, the button renders icon-only without a text alternative. Add a visually hidden fallback so screen readers have a name.<div className="flex items-center gap-1"> {iconConfig.shouldShowIcon && <FilterIcon className="size-4 text-custom-text-200" />} - {label} + {typeof label === "string" ? ( + label + ) : ( + <span className="sr-only">Filters</span> + )} </div>
22-24: Tighten the callback type to match the selected property
onFilterSelectcurrently typesidasstring, buthandleFilterSelectpasses aP. Make it(id: P) => voidfor type safety.- onFilterSelect?: (id: string) => void; + onFilterSelect?: (id: P) => void;
58-64: String is not localized"All filters applied" is hard-coded. Consider moving to i18n to keep consistency with the rest of the app.
apps/web/core/components/rich-filters/filters-row.tsx (1)
163-175: Observer wrapper likely unnecessary
ElementTransitionreads only React props and no observables. You can dropobserver(...)to avoid extra wrapper.packages/constants/src/rich-filters/option.ts (1)
55-66: Clarify the discriminant and align naming with UI props.Consider a clearer discriminated union and consistent naming with UI props (showOnMount vs isVisibleOnMount).
-export type TAutoVisibilityOptions = - | { - autoSetVisibility: true; - } - | { - autoSetVisibility: false; - isVisibleOnMount: boolean; - }; +export type TAutoVisibilityOptions = + | { mode: "auto" } + | { mode: "manual"; showOnMount: boolean };This reduces boolean‑flag ambiguity and matches the HOC/row prop terminology.
apps/web/core/components/views/form.tsx (1)
272-273: Switching tovariant="modal": verify a11y and focus management.Ensure the modal variant traps focus, sets aria‑modal/role, and restores focus on close. Quick sanity E2E recommended.
Suggested checks:
- Open Create/Update View → filters open by default, Esc closes, tab loop stays inside.
- Screen reader announces modal title.
apps/web/core/components/rich-filters/filters-toggle.tsx (1)
35-74: Hide the toggle until a filter instance existsWhen the store hasn’t created a filter yet,
filterisundefined, but we still render the clickable toggle. Users can click it, triggering the console error branch and leaving the control non-functional. Let’s skip rendering the interactive UI until the instance materializes so we don’t expose a dead control.- // Show the add filter button when there are no active conditions, the filter row is hidden, and no unsaved changes exist - if (filter && showAddFilterButton) { + if (!filter) { + return null; + } + + // Show the add filter button when there are no active conditions, the filter row is hidden, and no unsaved changes exist + if (showAddFilterButton) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (31)
apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx(2 hunks)apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx(5 hunks)apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx(5 hunks)apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/header.tsx(7 hunks)apps/web/core/components/issues/archived-issues-header.tsx(3 hunks)apps/web/core/components/issues/filters.tsx(2 hunks)apps/web/core/components/issues/issue-layouts/roots/all-issue-layout-root.tsx(1 hunks)apps/web/core/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx(1 hunks)apps/web/core/components/issues/issue-layouts/roots/cycle-layout-root.tsx(1 hunks)apps/web/core/components/issues/issue-layouts/roots/module-layout-root.tsx(1 hunks)apps/web/core/components/issues/issue-layouts/roots/project-layout-root.tsx(1 hunks)apps/web/core/components/issues/issue-layouts/roots/project-view-layout-root.tsx(1 hunks)apps/web/core/components/profile/profile-issues-filter.tsx(5 hunks)apps/web/core/components/profile/profile-issues.tsx(1 hunks)apps/web/core/components/rich-filters/add-filters-button.tsx(2 hunks)apps/web/core/components/rich-filters/filters-row.tsx(5 hunks)apps/web/core/components/rich-filters/filters-toggle.tsx(1 hunks)apps/web/core/components/views/form.tsx(2 hunks)apps/web/core/components/work-item-filters/filters-hoc/base.tsx(4 hunks)apps/web/core/components/work-item-filters/filters-hoc/shared.ts(1 hunks)apps/web/core/components/work-item-filters/filters-row.tsx(1 hunks)apps/web/core/components/work-item-filters/filters-toggle.tsx(1 hunks)apps/web/core/components/workspace/views/form.tsx(2 hunks)apps/web/core/hooks/store/work-item-filters/use-work-item-filter-instance.ts(1 hunks)packages/constants/src/rich-filters/option.ts(2 hunks)packages/shared-state/src/store/rich-filters/config-manager.ts(1 hunks)packages/shared-state/src/store/rich-filters/filter-helpers.ts(5 hunks)packages/shared-state/src/store/rich-filters/filter.ts(9 hunks)packages/shared-state/src/store/work-item-filters/filter.store.ts(4 hunks)packages/shared-state/src/store/work-item-filters/index.ts(1 hunks)packages/shared-state/src/store/work-item-filters/shared.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (21)
apps/web/core/components/views/form.tsx (2)
apps/space/core/store/publish/publish.store.ts (1)
workspaceSlug(93-95)apps/web/core/components/work-item-filters/filters-row.tsx (1)
WorkItemFiltersRow(12-12)
apps/web/core/components/workspace/views/form.tsx (1)
apps/web/core/components/work-item-filters/filters-row.tsx (1)
WorkItemFiltersRow(12-12)
apps/web/core/components/work-item-filters/filters-toggle.tsx (2)
apps/web/core/hooks/store/work-item-filters/use-work-item-filters.ts (1)
useWorkItemFilters(7-11)apps/web/core/components/rich-filters/filters-toggle.tsx (1)
FiltersToggle(17-76)
apps/web/core/components/work-item-filters/filters-hoc/base.tsx (1)
packages/shared-state/src/store/work-item-filters/shared.ts (1)
IWorkItemFilterInstance(8-8)
apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx (1)
apps/web/core/components/work-item-filters/filters-toggle.tsx (1)
WorkItemFiltersToggle(14-22)
packages/shared-state/src/store/rich-filters/config-manager.ts (1)
packages/shared-state/src/store/rich-filters/filter.ts (1)
IFilterInstance(64-113)
apps/web/core/components/work-item-filters/filters-hoc/shared.ts (2)
packages/types/src/view-props.ts (2)
TWorkItemFilterProperty(104-104)TWorkItemFilterExpression(120-120)packages/shared-state/src/store/work-item-filters/shared.ts (1)
IWorkItemFilterInstance(8-8)
apps/web/core/components/work-item-filters/filters-row.tsx (2)
apps/web/core/components/rich-filters/filters-row.tsx (1)
TFiltersRowProps(13-23)packages/shared-state/src/store/work-item-filters/shared.ts (1)
IWorkItemFilterInstance(8-8)
apps/web/core/hooks/store/work-item-filters/use-work-item-filter-instance.ts (1)
packages/shared-state/src/store/work-item-filters/shared.ts (1)
IWorkItemFilterInstance(8-8)
packages/shared-state/src/store/work-item-filters/filter.store.ts (3)
packages/constants/src/rich-filters/option.ts (1)
TExpressionOptions(44-48)packages/types/src/view-props.ts (1)
TWorkItemFilterExpression(120-120)packages/shared-state/src/store/work-item-filters/shared.ts (2)
TWorkItemFilterKey(6-6)IWorkItemFilterInstance(8-8)
apps/web/core/components/rich-filters/filters-row.tsx (1)
apps/web/core/components/rich-filters/add-filters-button.tsx (1)
TAddFilterButtonProps(10-24)
apps/web/core/components/issues/archived-issues-header.tsx (2)
apps/web/core/components/archives/archive-tabs-list.tsx (1)
ArchiveTabsList(32-64)apps/web/core/components/work-item-filters/filters-toggle.tsx (1)
WorkItemFiltersToggle(14-22)
packages/shared-state/src/store/rich-filters/filter.ts (4)
packages/types/src/rich-filters/expression.ts (1)
TFilterProperty(19-19)packages/types/src/rich-filters/adapter.ts (1)
TExternalFilter(7-7)packages/shared-state/src/store/rich-filters/filter-helpers.ts (1)
FilterInstanceHelper(64-230)packages/constants/src/rich-filters/option.ts (1)
DEFAULT_FILTER_VISIBILITY_OPTIONS(70-72)
apps/web/core/components/issues/filters.tsx (1)
apps/web/core/components/work-item-filters/filters-toggle.tsx (1)
WorkItemFiltersToggle(14-22)
packages/shared-state/src/store/work-item-filters/shared.ts (2)
packages/shared-state/src/store/rich-filters/filter.ts (1)
IFilterInstance(64-113)packages/types/src/view-props.ts (2)
TWorkItemFilterProperty(104-104)TWorkItemFilterExpression(120-120)
packages/shared-state/src/store/rich-filters/filter-helpers.ts (4)
packages/types/src/rich-filters/expression.ts (2)
TFilterProperty(19-19)TFilterExpression(86-88)packages/types/src/rich-filters/adapter.ts (2)
TExternalFilter(7-7)IFilterAdapter(14-23)packages/constants/src/rich-filters/option.ts (2)
TExpressionOptions(44-48)TAutoVisibilityOptions(58-65)packages/shared-state/src/store/rich-filters/filter.ts (2)
isVisible(216-218)IFilterInstance(64-113)
apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx (1)
apps/web/core/components/work-item-filters/filters-toggle.tsx (1)
WorkItemFiltersToggle(14-22)
apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx (1)
apps/web/core/components/work-item-filters/filters-toggle.tsx (1)
WorkItemFiltersToggle(14-22)
apps/web/core/components/rich-filters/filters-toggle.tsx (4)
packages/types/src/rich-filters/expression.ts (1)
TFilterProperty(19-19)packages/types/src/rich-filters/adapter.ts (1)
TExternalFilter(7-7)packages/shared-state/src/store/rich-filters/filter.ts (1)
IFilterInstance(64-113)apps/web/core/components/rich-filters/add-filters-button.tsx (1)
AddFilterButton(26-113)
apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/header.tsx (3)
apps/web/ce/components/views/helper.tsx (1)
GlobalViewLayoutSelection(13-13)apps/web/core/components/work-item-filters/filters-toggle.tsx (1)
WorkItemFiltersToggle(14-22)apps/web/core/components/issues/issue-layouts/filters/header/helpers/dropdown.tsx (1)
FiltersDropdown(25-124)
apps/web/core/components/profile/profile-issues-filter.tsx (1)
apps/web/core/components/work-item-filters/filters-toggle.tsx (1)
WorkItemFiltersToggle(14-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Cursor Bugbot
- GitHub Check: Analyze (javascript)
- GitHub Check: Build and lint web apps
🔇 Additional comments (17)
packages/shared-state/src/store/rich-filters/config-manager.ts (1)
61-61: Encapsulation winMaking
_filterInstanceprivate enforces the intended ownership boundary so callers can’t poke at the parent filter directly. Solid cleanup.packages/shared-state/src/store/rich-filters/filter-helpers.ts (1)
32-39: Visibility API addition looks goodAdding
isVisible,setInitialVisibility, andtoggleVisibilityto the helper’s public surface aligns with the PR goal and keeps consumers simple.Please confirm all call sites use
toggleVisibility(not settinghelper.isVisibledirectly).apps/web/core/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx (1)
12-12: Import path change looks goodSwitch to the consolidated
filters-rowpath keeps behavior unchanged.apps/web/core/components/issues/issue-layouts/roots/project-view-layout-root.tsx (1)
12-12: Import path change looks goodAligned with new
filters-rowmodule. No behavioral impact.apps/web/core/components/issues/filters.tsx (1)
106-107: Verify EPIC context filters and default toggle visibilityConfirm that ISSUE_STORE_TO_FILTERS_MAP provides display and property filters for EIssuesStoreType.EPIC so the toggle renders valid filters in the EPIC header, and that its default visibility matches existing UX expectations.
apps/web/core/components/profile/profile-issues.tsx (1)
13-13: Import path update aligns with filters refactor.Switching to the new
filters-rowmodule keeps this profile view in sync with the reorganized work-item filters components. ✔️apps/web/core/components/issues/issue-layouts/roots/cycle-layout-root.tsx (1)
15-123: Path realignment looks good.Pointing the cycle layout to
filters-rowmatches the shared filters module move and keeps the tracker wiring intact without altering runtime behavior.apps/web/core/components/issues/issue-layouts/roots/module-layout-root.tsx (1)
13-94: Consistent filters-row import.Good call updating this module root to the shared
filters-rowpath so all layouts rely on the same component surface.apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx (1)
40-213: Toggle integration fits perfectly.The header now exposes the shared
WorkItemFiltersTogglewith the cycle’s store identifiers, so users can hide/show the filters row without disturbing the existing display controls. Nice, cohesive placement ahead of the filters dropdown.packages/shared-state/src/store/work-item-filters/index.ts (1)
3-3: Re-export keeps public API complete.Including
./sharedensures downstream imports can pick upIWorkItemFilterInstanceand related helpers from the top-level store index. 👍apps/web/core/components/views/form.tsx (2)
25-25: Path update is consistent with module move.
267-268: ConfirmshowOnMountbehavior won’t cause SSR hydration flicker.If visibility derives from client state, ensure initial server markup matches (e.g., guard with useEffect or default visible state). Otherwise a brief flash may occur.
apps/web/core/components/issues/issue-layouts/roots/project-layout-root.tsx (1)
14-15: Import path swap is safe and consistent with other roots.apps/web/core/components/workspace/views/form.tsx (3)
23-24: Import path update aligns with new component location.
178-179:showOnMount: validate initial visibility for Workspace views.On create flows
entityIdis undefined; confirm HOC’s init path still shows filters immediately and doesn’t persist accidental state across form resets.
183-184: Modal variant parity with Project form.Good for UX consistency. Verify both forms share the same close/save semantics and keyboard shortcuts.
apps/web/core/components/issues/issue-layouts/roots/all-issue-layout-root.tsx (1)
13-13: Import path alias verified; no residual references to the old module.
WorkItemFiltersRow is exported identically from the new path with no side effects.
| this.isVisible = isVisible; | ||
| return; | ||
| } | ||
| this.isVisible = !this.isVisible; |
There was a problem hiding this comment.
Let's just use ?? here instead of an if-else.
There was a problem hiding this comment.
I feel this is more readable.
…ty (makeplane#7865) * [WEB-4969] feat: add toggle button for work item filters row visibility * fix: improve error handling in filter update and refine visibility condition check * chore: correct visibility toggle parameter in filter store
Description
This PR introduces a toggle button for filters across all work item layouts, allowing users to hide the filters bar.
Type of Change
Note
Adds a filters toggle across work item pages and implements filter visibility state (auto/show-on-mount), plus a modal variant of the filters row.
WorkItemFiltersToggleto headers forCYCLE,MODULE,PROJECT_VIEW,GLOBAL,ARCHIVED,PROFILE, and project issues.FiltersToggleand updateFiltersRowwith visibility control, animation, icon tweaks, and new"modal"variant; default label hidden where appropriate."@/components/work-item-filters/filters-row".isVisible,toggleVisibility, auto-visibility options, and initialization viaFilterInstanceHelper/FilterInstance.IWorkItemFilterInstance,TWorkItemFilterKey,showOnMountsupport, and visibility propagation.WorkItemFiltersRowmodal variant in project/workspace view forms withshowOnMount.Written by Cursor Bugbot for commit c4ee1db. This will update automatically on new commits. Configure here.
Summary by CodeRabbit
New Features
UI/UX