Skip to content

🐛 Filter button: icon-only with glow indicator#4370

Merged
clubanderson merged 1 commit intomainfrom
feat/merge-project-filter
Apr 3, 2026
Merged

🐛 Filter button: icon-only with glow indicator#4370
clubanderson merged 1 commit intomainfrom
feat/merge-project-filter

Conversation

@clubanderson
Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #4369 — removes text from the filter button to prevent navbar layout shift.

Before: Button showed "All" → "Filtered" → filter set name (text changes caused elements to jump)
After: Icon-only button with visual state indicators:

  • Default: muted funnel icon
  • Active: purple background + subtle glow + dot indicator in top-right
  • Clear button: compact X icon (also no text)

Test plan

  • Verify filter button is icon-only on Netlify preview
  • Verify no navbar shift when toggling filters on/off
  • Verify purple glow appears when filters are active
  • Verify clear (X) button works

Copilot AI review requested due to automatic review settings April 3, 2026 02:51
@kubestellar-prow
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign mikespreitzer for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubestellar-prow kubestellar-prow bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Apr 3, 2026
@netlify
Copy link
Copy Markdown

netlify bot commented Apr 3, 2026

Deploy Preview for kubestellarconsole canceled.

Name Link
🔨 Latest commit 93f82ce
🔍 Latest deploy log https://app.netlify.com/projects/kubestellarconsole/deploys/69cf2c288870990008c01387

@kubestellar-prow kubestellar-prow bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 3, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

👋 Hey @clubanderson — thanks for opening this PR!

🤖 This project is developed exclusively using AI coding assistants.

Please do not attempt to code anything for this project manually.
All contributions should be authored using an AI coding tool such as:

This ensures consistency in code style, architecture patterns, test coverage,
and commit quality across the entire codebase.


This is an automated message.

@kubestellar-prow kubestellar-prow bot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Apr 3, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR is a UI follow-up to #4369 that removes text from the navbar filter controls to prevent layout shift, while also consolidating the prior “Projects” concept into the unified filter dropdown (via cluster groups) and migrating legacy localStorage data.

Changes:

  • Converted the navbar filter trigger and clear action to icon-only buttons, adding a visual “active” glow/dot indicator.
  • Added a “Projects” (cluster groups) section inside the unified filter dropdown, including create/delete and color selection.
  • Removed the legacy project filter implementation (context + selector + definitions) and added a one-time localStorage migration into clusterGroups.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
web/src/lib/projects.ts Removed legacy project definitions/constants (projects feature retired).
web/src/hooks/useProjectFilter.tsx Removed legacy project filter context/provider and CRUD logic.
web/src/hooks/useGlobalFilters.tsx Added migration from legacy projects storage to cluster groups.
web/src/components/layout/navbar/ProjectSelector.tsx Removed legacy project selector dropdown from navbar.
web/src/components/layout/navbar/Navbar.tsx Switched navbar to use only the unified ClusterFilterPanel.
web/src/components/layout/navbar/ClusterFilterPanel.tsx Implemented icon-only filter/clear buttons + “Projects” section UI and group color support.
web/src/App.tsx Removed ProjectFilterProvider from the app provider tree.

// Initialize cluster groups from localStorage
// Initialize cluster groups from localStorage (+ migrate legacy projects)
const [clusterGroups, setClusterGroups] = useState<ClusterGroup[]>(() => {
let groups: ClusterGroup[] = DEFAULT_GROUPS
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the cluster-group initializer, groups is assigned to DEFAULT_GROUPS and then later mutated via groups.push(...). This mutates the DEFAULT_GROUPS constant array by reference (even though it’s currently empty), which can cause surprising shared-state behavior. Initialize with a new array (e.g., spread/copy) before mutating.

Suggested change
let groups: ClusterGroup[] = DEFAULT_GROUPS
let groups: ClusterGroup[] = [...DEFAULT_GROUPS]

Copilot uses AI. Check for mistakes.
Comment on lines +169 to +173
const existingNames = new Set(groups.map(g => g.name))
for (const p of projects) {
if (!existingNames.has(p.name)) {
groups.push({
id: `migrated-${p.id}`,
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

During legacy projects → cluster groups migration, existingNames is never updated after adding a migrated group. If multiple legacy projects share the same name, this loop can insert duplicates in a single run. Add the name to existingNames when a group is appended (or otherwise dedupe).

Copilot uses AI. Check for mistakes.
Comment on lines +165 to +183
const projects = JSON.parse(oldProjects) as Array<{
id: string; name: string; clusters: string[]; color?: string
}>
if (Array.isArray(projects) && projects.length > 0) {
const existingNames = new Set(groups.map(g => g.name))
for (const p of projects) {
if (!existingNames.has(p.name)) {
groups.push({
id: `migrated-${p.id}`,
name: p.name,
clusters: p.clusters || [],
color: p.color,
})
}
}
localStorage.setItem(GROUPS_STORAGE_KEY, JSON.stringify(groups))
}
localStorage.removeItem('projects:definitions')
localStorage.removeItem('projects:selected')
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The migration removes projects:definitions / projects:selected whenever the key exists and JSON.parse succeeds, even if the parsed value isn’t a valid projects array or nothing was migrated. This can permanently drop user data. Only remove the legacy keys after validating the parsed structure and successfully migrating.

Suggested change
const projects = JSON.parse(oldProjects) as Array<{
id: string; name: string; clusters: string[]; color?: string
}>
if (Array.isArray(projects) && projects.length > 0) {
const existingNames = new Set(groups.map(g => g.name))
for (const p of projects) {
if (!existingNames.has(p.name)) {
groups.push({
id: `migrated-${p.id}`,
name: p.name,
clusters: p.clusters || [],
color: p.color,
})
}
}
localStorage.setItem(GROUPS_STORAGE_KEY, JSON.stringify(groups))
}
localStorage.removeItem('projects:definitions')
localStorage.removeItem('projects:selected')
const parsedProjects: unknown = JSON.parse(oldProjects)
const isValidProject = (
project: unknown
): project is { id: string; name: string; clusters: string[]; color?: string } => {
if (!project || typeof project !== 'object') {
return false
}
const candidate = project as {
id?: unknown; name?: unknown; clusters?: unknown; color?: unknown
}
return (
typeof candidate.id === 'string' &&
typeof candidate.name === 'string' &&
Array.isArray(candidate.clusters) &&
candidate.clusters.every(cluster => typeof cluster === 'string') &&
(candidate.color === undefined || typeof candidate.color === 'string')
)
}
if (Array.isArray(parsedProjects) && parsedProjects.length > 0 && parsedProjects.every(isValidProject)) {
const existingNames = new Set(groups.map(g => g.name))
const migratedGroups: ClusterGroup[] = []
for (const p of parsedProjects) {
if (!existingNames.has(p.name)) {
migratedGroups.push({
id: `migrated-${p.id}`,
name: p.name,
clusters: p.clusters,
color: p.color,
})
existingNames.add(p.name)
}
}
if (migratedGroups.length > 0) {
const nextGroups = [...groups, ...migratedGroups]
localStorage.setItem(GROUPS_STORAGE_KEY, JSON.stringify(nextGroups))
groups = nextGroups
localStorage.removeItem('projects:definitions')
localStorage.removeItem('projects:selected')
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +177 to 181
<button
onClick={() => {
selectAllClusters()
selectAllSeverities()
selectAllStatuses()
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clear-filters button is now icon-only and relies on title for its description. title is not a reliable accessible name for screen readers. Add an aria-label (and consider type="button") so the control is accessible.

Copilot uses AI. Check for mistakes.
Comment on lines 193 to 197
<button
onClick={() => setShowClusterFilter(!showClusterFilter)}
className={cn(
'flex items-center gap-2 px-3 py-1.5 rounded-lg transition-colors',
'relative flex items-center justify-center w-8 h-8 rounded-lg transition-colors',
isFiltered
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The filter toggle button is icon-only and relies on title. Please add an aria-label and expose state via aria-pressed={isFiltered} (or similar) so assistive tech can understand and operate the control.

Copilot uses AI. Check for mistakes.
</button>
<button
onClick={() => deleteClusterGroup(group.id)}
className="p-1 text-muted-foreground hover:text-red-400 opacity-0 group-hover/item:opacity-100 transition-all flex-shrink-0"
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cluster-group delete button (trash icon) relies on title and has no accessible name for screen readers. Add an aria-label so the action is announced correctly.

Suggested change
className="p-1 text-muted-foreground hover:text-red-400 opacity-0 group-hover/item:opacity-100 transition-all flex-shrink-0"
className="p-1 text-muted-foreground hover:text-red-400 opacity-0 group-hover/item:opacity-100 transition-all flex-shrink-0"
aria-label={t('common:filters.deleteGroup', 'Delete group')}

Copilot uses AI. Check for mistakes.
Comment on lines +321 to +325
{GROUP_COLORS.map(c => (
<button
key={c}
onClick={() => setNewGroupColor(c)}
className={cn(
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The color picker swatch buttons are purely visual and currently have no accessible name/state. Add an aria-label per color and expose selection via aria-pressed (or an equivalent pattern) so the picker is usable with assistive tech.

Copilot uses AI. Check for mistakes.
Remove "All"/"Filtered" text label from filter button to prevent
navbar layout shift. Now shows just the funnel icon with:
- Purple background + glow when filters are active
- Small purple dot indicator in top-right corner
- Compact X button to clear filters (also icon-only)

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
@clubanderson clubanderson force-pushed the feat/merge-project-filter branch from 22b4a61 to 93f82ce Compare April 3, 2026 02:55
@kubestellar-prow kubestellar-prow bot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. and removed needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Apr 3, 2026
@clubanderson clubanderson merged commit 255143b into main Apr 3, 2026
18 of 19 checks passed
@kubestellar-prow kubestellar-prow bot deleted the feat/merge-project-filter branch April 3, 2026 03:01
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

Thank you for your contribution! Your PR has been merged.

Check out what's new:

Stay connected: Slack #kubestellar-dev | Multi-Cluster Survey

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

Post-merge build verification passed

Both Go and frontend builds compiled successfully against merge commit 255143bcd6b8745005262a2ea801b04ef5c8fced.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

✅ Post-Merge Verification: passed

Commit: 255143bcd6b8745005262a2ea801b04ef5c8fced
Specs run: navbar-responsive.spec.ts smoke.spec.ts
Report: https://github.com/kubestellar/console/actions/runs/23931829179

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

Labels

dco-signoff: yes Indicates the PR's author has signed the DCO. size/M Denotes a PR that changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants