Skip to content

feat(filter-cascade): add FilterCascade — a FilterDropdown over a hierarchy - #345

Open
ispashkov wants to merge 8 commits into
mainfrom
feat/filter-cascade
Open

ispashkov wants to merge 8 commits into
mainfrom
feat/filter-cascade

Conversation

@ispashkov

Copy link
Copy Markdown
Collaborator

What type of PR is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update

Description

New FilterCascade — the FilterDropdown pill over a hierarchy (organization › deployment › application, region › zone). The menu opens each level beside the previous one on hover; a node at any depth can be picked, and the trigger reads the path: «Scope · ● a › ● b» with a ✕.

Built on Zag's cascade-select — Ark UI 5.39 does not wrap it yet — so @zag-js/cascade-select and @zag-js/react (1.43.3, already in the lockfile via Ark) become direct dependencies.

Compound, following FilterDropdown:

<FilterCascade label="Scope" collection={collection} value={path} onValueChange={…}>
  <FilterCascadeTrigger>
    <FilterCascadeClear />                       {/* optional — replaces the default ✕ */}
  </FilterCascadeTrigger>
  <FilterCascadeContent>
    <FilterCascadeSearch />                      {/* pinned on top, from 8 top-level items */}
    <Levels />                                   {/* useFilterCascade().levels → FilterCascadeLevel / FilterCascadeItem */}
    <FilterCascadeSection>                       {/* full-width rows under the levels */}
      <FilterCascadeCheckboxItem checked={…} onCheckedChange={…}>Show organization policies</FilterCascadeCheckboxItem>
    </FilterCascadeSection>
  </FilterCascadeContent>
</FilterCascade>
  • useFilterCascade() gives the open levels (the top level plus one per highlighted branch), the picked path, query, isEmpty.
  • Parts: FilterCascadeLevel, FilterCascadeItem (+ ItemText / ItemDescription / ItemIcon, defaulting to the node's fields; › on branches, ✓ on the picked leaf), FilterCascadeGroupLabel, FilterCascadeSearch, FilterCascadeEmpty, FilterCascadeSection, FilterCascadeCheckboxItem, FilterCascadeClear, FilterCascadeLevels.
  • With no children, FilterCascadeContent renders every level by itself — the simple case stays one line.
  • Value is one path (string[], [] = unset). Parent nodes are pickable by default (allowParentSelection).

Worth a look:

  • Zag gaps worked around: selectedItems is empty for a defaultValue (the path is derived from value); data-autofocus is not honoured (the search focuses itself on open); its keymap listens on the content only (↓ in the search hands the list over).
  • The positioner stays mounted — Zag cannot place a panel that mounts on open; it hides the content itself.
  • The pill lifts FilterDropdown's 180px cap with max-w-full: max-w-none is the 0 spacing token in this theme.

First consumer: the Scope filter of Flow management (Figma: Deployment › Scope level filters). The Composed story reproduces it.

Related Tickets & Documents

  • No WDS ticket yet. Consumer: CORE-2627 / CORE-2629 (Flow management Scope filter).

QA Instructions

Storybook → Patterns / FilterCascade:

  • Default — hover a deployment, the applications column opens; pick an application or the deployment itself.
  • Picked / Parent Picked — the trigger reads the path; ✕ or Backspace on the trigger clears it.
  • Composed — search narrows deployments, «No results» when nothing matches; from the search ↓ → → ↓ Enter picks with the keyboard; «Show organization policies» toggles without closing the menu or changing the pick.

Added/updated tests?

  • Yes — unit (FilterCascade.test.tsx, 8: picking, parent pick, clear, composed parts with consumer attributes, search + empty, keyboard handoff, section toggle) and e2e (FilterCascade.e2e.ts: 6 visual + 2 behaviour). Baselines are generated by CI ([update-screenshots] on the last commit). Analytics targets: ANALYTICS_GAPS.md.
  • No, and this is why: please replace this line with details on why tests
    have not been included
  • I need help with writing tests

🤖 Generated with Claude Code

ispashkov and others added 5 commits October 6, 2026 14:20
…hierarchy

Built on Zag cascade-select (Ark UI does not wrap it yet): levels open side
by side on hover, a node at any depth can be picked, the trigger reads the
path. First cut for the Flow management Scope filter; search and toggle rows
come next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s, clear

useFilterCascade exposes the open levels; FilterCascadeLevel and FilterCascadeItem
(+ ItemText / ItemDescription / ItemIcon) render them, with FilterCascadeGroupLabel,
FilterCascadeSearch over the top level, FilterCascadeEmpty, FilterCascadeSection with
FilterCascadeCheckboxItem, and a replaceable FilterCascadeClear. With no children,
FilterCascadeContent still renders every level on its own. Every click target is an
exported part, so the analytics gaps of the first cut are closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…screenshots]

Trigger states, the next level on hover, the composed Scope menu with search,
groups and a section, no results, and picking a path with the keyboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…shots]

Zag's default scroll brings the deepest highlighted item into view inside every
level, so the first column scrolled when the highlight moved into the next one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ispashkov

Copy link
Copy Markdown
Collaborator Author

CI note: the 4 failing e2e tests in OverflowList.e2e.ts (ChipOverflowPopover — Show All / Hidden Only / Overlay Origin) are not from this PR — it does not touch OverflowList, and they fail the same way locally on this branch. They seem to come from main after WDS-159, whose last run had [update-screenshots], so the regular e2e tests were skipped there.

🤖 Generated with Claude Code

ispashkov and others added 3 commits October 6, 2026 18:36
…ots]

Playwright scrolled the still-animating item into view, which scrolled the first
level in the baseline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…enshots]

locator.hover() scrolls its target into view, which scrolled the first level in
the baselines. Move the mouse instead, and drop the two stale baselines so CI
records them again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant