feat(catalog): sticky category quick-nav (icon jump chips)#549
Conversation
The catalog lists 150+ anchors across 15 categories on one long page with no in-page wayfinding. Adds a sticky quick-nav above the card grid: one icon chip per non-empty category with a live anchor count, linking to that category section. - Plain in-page anchors (#category-<id>) — works without JavaScript; sections get a matching id and scroll-margin-top so the heading clears the sticky bar. - Icon-only chips in a single horizontally scrollable row keep the sticky bar short (~40px vs ~120px wrapped); the category name is the accessible label (title + aria-label), the icon is decorative. - Assigned unique icons to all 14 rendered categories (strategic-planning ♟️ instead of a duplicate 🎯; added ✍️ creative-writing, 🧠 knowledge-management) — also fixes the section-heading fallback icons. - Added the missing categories.creative-writing translation (EN/DE); the heading previously showed the raw i18n key. - Chip counts stay in sync with the search/role filter, and emptied categories drop out of the nav. - Hidden below the sm breakpoint: on phones, search + role filter cover navigation and the sticky strip would waste vertical space. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Caution Review failedPull request was closed or merged during review No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughDiese Pull Request ergänzt eine Kategorie-Schnellnavigation mit Jump-Links zum Card-Grid. Die CATEGORY_ICONS werden um neue Kategorien erweitert, eine Sticky-Navigation rendert Jump-Links für nicht-leere Kategorien mit Live-Zählern, Filter-Logik synchronisiert die Navigation basierend auf Kartensichtbarkeit, Styling implementiert das responsive Layout, und neue Tests sowie Übersetzungen dokumentieren die Funktionalität. ChangesKategorie-Schnellnavigation mit Jump-Links
🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
website/src/styles/main.css (1)
140-142: ⚡ Quick winSmooth-Scroll sollte Reduced-Motion respektieren
scroll-behavior: smoothist global aktiv, aber ohne Fallback für Nutzer mit reduzierter Bewegung. Bitte perprefers-reduced-motion: reduceaufautozurückschalten.Vorgeschlagener Fix
html { scroll-behavior: smooth; } + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@website/src/styles/main.css` around lines 140 - 142, Die globale Regel "html { scroll-behavior: smooth; }" überschreibt Nutzerpräferenzen für reduzierte Bewegung; fügen Sie eine CSS-Media-Query für "prefers-reduced-motion: reduce" hinzu und setzen darin die Regel für den "html" Selektor auf "scroll-behavior: auto" (ggf. mit !important) so dass Nutzer mit reduziertem Motion-Setting wieder die Standard-Scrollverhalten erhalten.website/src/components/card-grid.js (1)
504-509: ⚡ Quick winLeere Quick-Nav komplett ausblenden, wenn alle Chips weggefiltert sind.
Aktuell werden nur einzelne
<li>-Einträge versteckt. Wenn alle Kategorien leer sind, bleibt ein leerer sticky Container sichtbar.🔧 Vorschlag
function syncCategoryNav() { + let visibleItems = 0 document.querySelectorAll('.category-nav-link').forEach((link) => { @@ const item = link.closest('li') - if (item) item.style.display = visible > 0 ? '' : 'none' + if (item) { + const show = visible > 0 + item.style.display = show ? '' : 'none' + if (show) visibleItems++ + } @@ }) + + const nav = document.querySelector('.category-nav') + if (nav) nav.style.display = visibleItems > 0 ? '' : 'none' }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@website/src/components/card-grid.js` around lines 504 - 509, Beim Aktualisieren der einzelnen <li> und der .category-nav-counts berechne anschließend, ob über alle Links hinweg irgendein visible > 0 existiert; wenn nicht, verstecke den gesamten Quick-Nav-Container (z. B. das umgebende Sticky-Element) statt nur die einzelnen Listeneinträge. Konkret: nach der Schleife summiere die visible-Werte (oder prüfe, ob mindestens ein countEl.textContent !== '0'), finde das umgebende Wrapper-Element (z. B. via link.closest('<wrapper-selector>') oder document.querySelector('.quick-nav' / '.category-quick-nav') ) und setze dessen style.display = 'none' wenn die Summe 0 ist, sonst zeige es wieder (''), damit der leere sticky Container vollständig verschwindet.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@website/src/components/card-grid.js`:
- Around line 124-126: Das aria-label der Quick-Nav wird nur beim initialen
Rendern gesetzt und nicht bei späteren Sprachwechseln aktualisiert; aktualisiere
die Stelle, die das <nav class="category-nav"> rendert (wo
aria-label="${escapeHtml(i18n.t('nav.categoryJump'))}" gesetzt wird) so, dass
zusätzlich ein data-i18n-aria="nav.categoryJump" Attribut hinzugefügt wird,
damit applyTranslations() die Übersetzung später aktualisieren kann; stelle
sicher, dass der Schlüssel "nav.categoryJump" verwendet wird (gleich wie beim
aktuellen i18n.t-Aufruf) und dass escapeHtml weiterhin für die initiale
aria-label-Werteinsetzung verwendet wird.
In `@website/src/styles/main.css`:
- Around line 100-138: The CSS rules violate the declaration-empty-line-before
lint rule; update each selector block (.category-nav, .category-nav-list,
.category-nav-link, .category-nav-count) to insert a blank line before the
relevant declarations (e.g., before the `@apply` lines, before
background/border/color declarations) so there is an empty line separating
declaration groups as required by declaration-empty-line-before; keep the
existing properties and order, only add the missing blank lines to satisfy
Stylelint.
---
Nitpick comments:
In `@website/src/components/card-grid.js`:
- Around line 504-509: Beim Aktualisieren der einzelnen <li> und der
.category-nav-counts berechne anschließend, ob über alle Links hinweg irgendein
visible > 0 existiert; wenn nicht, verstecke den gesamten Quick-Nav-Container
(z. B. das umgebende Sticky-Element) statt nur die einzelnen Listeneinträge.
Konkret: nach der Schleife summiere die visible-Werte (oder prüfe, ob mindestens
ein countEl.textContent !== '0'), finde das umgebende Wrapper-Element (z. B. via
link.closest('<wrapper-selector>') oder document.querySelector('.quick-nav' /
'.category-quick-nav') ) und setze dessen style.display = 'none' wenn die Summe
0 ist, sonst zeige es wieder (''), damit der leere sticky Container vollständig
verschwindet.
In `@website/src/styles/main.css`:
- Around line 140-142: Die globale Regel "html { scroll-behavior: smooth; }"
überschreibt Nutzerpräferenzen für reduzierte Bewegung; fügen Sie eine
CSS-Media-Query für "prefers-reduced-motion: reduce" hinzu und setzen darin die
Regel für den "html" Selektor auf "scroll-behavior: auto" (ggf. mit !important)
so dass Nutzer mit reduziertem Motion-Setting wieder die
Standard-Scrollverhalten erhalten.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: e331b89f-2d3b-4ad6-8f4b-bfb0bb8931b7
📒 Files selected for processing (5)
website/src/components/card-grid.jswebsite/src/components/card-grid.test.jswebsite/src/styles/main.csswebsite/src/translations/de.jsonwebsite/src/translations/en.json
| .category-nav { | ||
| position: sticky; | ||
| top: 0; | ||
| z-index: 30; | ||
| /* Hidden on phones (search + role filter cover navigation there, and the | ||
| sticky strip would eat scarce vertical space); shown from sm upward. */ | ||
| @apply hidden sm:block mb-8 py-2; | ||
| background: var(--color-bg); | ||
| border-bottom: 1px solid var(--color-border); | ||
| } | ||
|
|
||
| .category-nav-list { | ||
| @apply flex flex-nowrap gap-2 list-none p-0 m-0 overflow-x-auto; | ||
| scrollbar-width: thin; | ||
| } | ||
|
|
||
| .category-nav-link { | ||
| @apply inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full no-underline whitespace-nowrap flex-shrink-0; | ||
| @apply transition-colors duration-150; | ||
| border: 1px solid var(--color-border); | ||
| background: var(--color-bg-secondary); | ||
| color: var(--color-text-secondary); | ||
| } | ||
|
|
||
| .category-nav-link:hover, | ||
| .category-nav-link:focus-visible { | ||
| color: var(--color-text); | ||
| border-color: var(--color-primary); | ||
| } | ||
|
|
||
| .category-nav-icon { | ||
| @apply text-base leading-none; | ||
| } | ||
|
|
||
| .category-nav-count { | ||
| @apply text-xs font-semibold px-1.5 rounded-full; | ||
| background: var(--color-bg); | ||
| color: var(--color-text-secondary); | ||
| } |
There was a problem hiding this comment.
Stylelint-Fehler in der neuen Quick-Nav-CSS beheben
In diesem Block fehlen die geforderten Leerzeilen vor einzelnen Deklarationen (u. a. Line 107, Line 113, Line 119, Line 136), was aktuell gegen declaration-empty-line-before verstößt und CI/Lint brechen kann.
Vorgeschlagener Fix
.category-nav {
position: sticky;
top: 0;
z-index: 30;
/* Hidden on phones (search + role filter cover navigation there, and the
sticky strip would eat scarce vertical space); shown from sm upward. */
`@apply` hidden sm:block mb-8 py-2;
+
background: var(--color-bg);
border-bottom: 1px solid var(--color-border);
}
.category-nav-list {
`@apply` flex flex-nowrap gap-2 list-none p-0 m-0 overflow-x-auto;
+
scrollbar-width: thin;
}
.category-nav-link {
`@apply` inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full no-underline whitespace-nowrap flex-shrink-0;
`@apply` transition-colors duration-150;
+
border: 1px solid var(--color-border);
background: var(--color-bg-secondary);
color: var(--color-text-secondary);
}
.category-nav-count {
`@apply` text-xs font-semibold px-1.5 rounded-full;
+
background: var(--color-bg);
color: var(--color-text-secondary);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .category-nav { | |
| position: sticky; | |
| top: 0; | |
| z-index: 30; | |
| /* Hidden on phones (search + role filter cover navigation there, and the | |
| sticky strip would eat scarce vertical space); shown from sm upward. */ | |
| @apply hidden sm:block mb-8 py-2; | |
| background: var(--color-bg); | |
| border-bottom: 1px solid var(--color-border); | |
| } | |
| .category-nav-list { | |
| @apply flex flex-nowrap gap-2 list-none p-0 m-0 overflow-x-auto; | |
| scrollbar-width: thin; | |
| } | |
| .category-nav-link { | |
| @apply inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full no-underline whitespace-nowrap flex-shrink-0; | |
| @apply transition-colors duration-150; | |
| border: 1px solid var(--color-border); | |
| background: var(--color-bg-secondary); | |
| color: var(--color-text-secondary); | |
| } | |
| .category-nav-link:hover, | |
| .category-nav-link:focus-visible { | |
| color: var(--color-text); | |
| border-color: var(--color-primary); | |
| } | |
| .category-nav-icon { | |
| @apply text-base leading-none; | |
| } | |
| .category-nav-count { | |
| @apply text-xs font-semibold px-1.5 rounded-full; | |
| background: var(--color-bg); | |
| color: var(--color-text-secondary); | |
| } | |
| .category-nav { | |
| position: sticky; | |
| top: 0; | |
| z-index: 30; | |
| /* Hidden on phones (search + role filter cover navigation there, and the | |
| sticky strip would eat scarce vertical space); shown from sm upward. */ | |
| `@apply` hidden sm:block mb-8 py-2; | |
| background: var(--color-bg); | |
| border-bottom: 1px solid var(--color-border); | |
| } | |
| .category-nav-list { | |
| `@apply` flex flex-nowrap gap-2 list-none p-0 m-0 overflow-x-auto; | |
| scrollbar-width: thin; | |
| } | |
| .category-nav-link { | |
| `@apply` inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full no-underline whitespace-nowrap flex-shrink-0; | |
| `@apply` transition-colors duration-150; | |
| border: 1px solid var(--color-border); | |
| background: var(--color-bg-secondary); | |
| color: var(--color-text-secondary); | |
| } | |
| .category-nav-link:hover, | |
| .category-nav-link:focus-visible { | |
| color: var(--color-text); | |
| border-color: var(--color-primary); | |
| } | |
| .category-nav-icon { | |
| `@apply` text-base leading-none; | |
| } | |
| .category-nav-count { | |
| `@apply` text-xs font-semibold px-1.5 rounded-full; | |
| background: var(--color-bg); | |
| color: var(--color-text-secondary); | |
| } |
🧰 Tools
🪛 Stylelint (17.12.0)
[error] 107-107: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
[error] 113-113: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
[error] 119-119: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
[error] 136-136: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@website/src/styles/main.css` around lines 100 - 138, The CSS rules violate
the declaration-empty-line-before lint rule; update each selector block
(.category-nav, .category-nav-list, .category-nav-link, .category-nav-count) to
insert a blank line before the relevant declarations (e.g., before the `@apply`
lines, before background/border/color declarations) so there is an empty line
separating declaration groups as required by declaration-empty-line-before; keep
the existing properties and order, only add the missing blank lines to satisfy
Stylelint.
The category quick-nav's <nav aria-label> was set only at render time, so a later language switch left it stale (applyTranslations only updates elements carrying data-i18n-aria). Add data-i18n-aria="nav.categoryJump" so the label follows the active language, matching the chips. Addresses CodeRabbit review on LLM-Coding#549. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem
The catalog lists 150+ anchors across 15 categories on a single long page with no in-page wayfinding — you scroll through every section to reach one category.
Solution
A sticky quick-nav above the card grid: one icon chip per non-empty category with a live anchor count, jumping to that category's section.
#category-<id>). Works with JavaScript disabled; sections get a matchingid+scroll-margin-topso the heading clears the sticky bar.title+aria-label); the icon isaria-hidden.strategic-planningnow ♟️ instead of a duplicate 🎯, plus ✍️creative-writingand 🧠knowledge-management(these also fix the section-heading fallback 📌).categories.creative-writingtranslation (EN/DE) — the heading was showing the raw i18n key.sm: on phones, search + role filter already cover navigation and the sticky strip would waste scarce vertical space.Tests
5 new unit tests for the nav contract (jump link per non-empty category, section ids, counts, accessible icon label). All 96 unit tests pass; lint + prettier clean.
Verification (preview build)
scroll-margin-toplands the heading below the sticky bar (verified via scrollIntoView positioning).display:none< 640px,block≥ 640px.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Style
Tests