Repository navigation
fix: update custom map legend panel when exporting image - #372
Conversation
📝 WalkthroughWalkthroughMap legend component gained an Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 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: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/kepler/src/components/CustomMapLegend.tsx (2)
166-177:⚠️ Potential issue | 🔴 CriticalVisibility toggle button is permanently hidden in all modes, not just export
hiddenis hardcoded unconditionally in theclassName, so the eye toggle is invisible even in normal (non-export) mode. Users can no longer toggle layer visibility from the legend panel at all. Per the PR objective, the button should only be hidden whenisExportistrue.🐛 Proposed fix
- <Button - className="h-7 w-7 hidden" - variant="ghost" - size="icon" - onClick={handleToggleVisibility} - > + <Button + className={`h-7 w-7${isExport ? ' hidden' : ''}`} + variant="ghost" + size="icon" + onClick={handleToggleVisibility} + >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/kepler/src/components/CustomMapLegend.tsx` around lines 166 - 177, The visibility toggle Button in CustomMapLegend is always hidden because the string "hidden" is unconditionally in its className; update the Button rendering (the Button that calls handleToggleVisibility and checks layer.config.isVisible) to remove the unconditional "hidden" and only apply "hidden" when the prop/state isExport is true (e.g., build className conditionally or use a classnames utility) so the eye toggle is visible in normal mode and hidden in export mode.
147-149:⚠️ Potential issue | 🟡 MinorGuard at lines 147–149 is dead code given the current unconditional filter
Because the parent already strips all invisible layers via
.filter((layer) => layer.config.isVisible)before passing them toLayerLegendItem,layer.config.isVisibleis alwaystrueat this point, makingisExport && !layer.config.isVisiblepermanentlyfalse. Once the filter above is fixed to be!isExport || layer.config.isVisible, this guard becomes meaningful and should be kept — so no removal needed, just noting the dependency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/kepler/src/components/CustomMapLegend.tsx` around lines 147 - 149, The parent currently filters out invisible layers unconditionally, making the guard in LayerLegendItem (the if (isExport && !layer.config.isVisible) return null; check) dead; update the parent filter logic to respect export mode by changing its predicate to allow invisible layers when not exporting (use !isExport || layer.config.isVisible) so the guard in LayerLegendItem becomes meaningful, and leave the existing guard inside LayerLegendItem intact; locate the parent use of .filter(...) that prepares layers for rendering in CustomMapLegend and adjust it accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/kepler/src/components/CustomMapLegend.tsx`:
- Around line 82-94: The legend currently filters out layers with
layer.config.isVisible unconditionally; change the logic so the .filter(...) is
only applied when isExport is true. Locate the JSX mapping over layers in
CustomMapLegend (the .map that renders LayerLegendItem) and conditionally
compute the list to map from: if isExport is true use layers.filter(l =>
l.config.isVisible), otherwise use layers as-is; keep passing key/index, layer,
containerW, isExport and {...restProps} to LayerLegendItem unchanged.
---
Outside diff comments:
In `@packages/kepler/src/components/CustomMapLegend.tsx`:
- Around line 166-177: The visibility toggle Button in CustomMapLegend is always
hidden because the string "hidden" is unconditionally in its className; update
the Button rendering (the Button that calls handleToggleVisibility and checks
layer.config.isVisible) to remove the unconditional "hidden" and only apply
"hidden" when the prop/state isExport is true (e.g., build className
conditionally or use a classnames utility) so the eye toggle is visible in
normal mode and hidden in export mode.
- Around line 147-149: The parent currently filters out invisible layers
unconditionally, making the guard in LayerLegendItem (the if (isExport &&
!layer.config.isVisible) return null; check) dead; update the parent filter
logic to respect export mode by changing its predicate to allow invisible layers
when not exporting (use !isExport || layer.config.isVisible) so the guard in
LayerLegendItem becomes meaningful, and leave the existing guard inside
LayerLegendItem intact; locate the parent use of .filter(...) that prepares
layers for rendering in CustomMapLegend and adjust it accordingly.
| {layers | ||
| .filter((layer) => layer.config.isVisible) | ||
| .map((layer, index) => { | ||
| return ( | ||
| <LayerLegendItem | ||
| key={index} | ||
| layer={layer} | ||
| containerW={containerW} | ||
| isExport={isExport} | ||
| {...restProps} | ||
| /> | ||
| ); | ||
| })} |
There was a problem hiding this comment.
Layer filter runs unconditionally, breaking non-export visibility management
.filter((layer) => layer.config.isVisible) is applied regardless of isExport. In normal mode, layers toggled off disappear from the legend with no way to re-enable them through this panel (and the toggle button is also hidden per the bug above). Filtering should only apply during export.
🐛 Proposed fix
- {layers
- .filter((layer) => layer.config.isVisible)
- .map((layer, index) => {
+ {layers
+ .filter((layer) => !isExport || layer.config.isVisible)
+ .map((layer, index) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/kepler/src/components/CustomMapLegend.tsx` around lines 82 - 94, The
legend currently filters out layers with layer.config.isVisible unconditionally;
change the logic so the .filter(...) is only applied when isExport is true.
Locate the JSX mapping over layers in CustomMapLegend (the .map that renders
LayerLegendItem) and conditionally compute the list to map from: if isExport is
true use layers.filter(l => l.config.isVisible), otherwise use layers as-is;
keep passing key/index, layer, containerW, isExport and {...restProps} to
LayerLegendItem unchanged.
There was a problem hiding this comment.
Hidden layers should not show in the legend panel irregardless of the isExport
There was a problem hiding this comment.
@bdjulbic, thank you for the clarification! I understand now that the Legend panel is designed to display only visible layers, regardless of whether it's in export mode or normal mode. The filter .filter((layer) => layer.config.isVisible) is working as intended.
The visibility toggling functionality is handled in the MAP SETTINGS panel (as shown in your screenshots), not in the Legend panel. The Legend panel's purpose is solely to display the legend information for layers that are currently visible on the map.
I'll note this design decision for future reviews.
✏️ Learnings added
Learnt from: bdjulbic
Repo: sqlrooms/sqlrooms PR: 372
File: packages/kepler/src/components/CustomMapLegend.tsx:82-94
Timestamp: 2026-02-20T14:09:18.651Z
Learning: In the CustomMapLegend component (packages/kepler/src/components/CustomMapLegend.tsx), the Legend panel should only display layers where layer.config.isVisible is true, regardless of whether it's in export mode or normal mode. The filter `.filter((layer) => layer.config.isVisible)` before mapping layers is intentional and correct. Visibility toggling is handled in the MAP SETTINGS panel, not in the Legend panel.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
There was a problem hiding this comment.
🧹 Nitpick comments (3)
packages/kepler/src/components/CustomMapLegend.tsx (3)
149-149: Duplicate<style>tag injected per layer item — move to a single injection point.A new
<style>element is created and inserted into the DOM for every renderedLayerLegendItem. With N layers, the browser receives N identical style blocks to parse and apply. Move this rule to a module-level CSS file, a single top-level<style>tag at theMapLegendrender root, or acreateGlobalStyle/injectGlobalequivalent.Additionally, this rule fires unconditionally, so kepler.gl's panel header actions are suppressed even in non-export mode (assuming the intent is export-only).
♻️ Proposed refactor — hoist style to component root, guarded by `isExport`
Remove the per-item
<style>tag fromLayerLegendItem:- <style>{`.legend--layer__item .panel--header__action { display: none !important; }`}</style>Inject once at the
MapLegendlevel, conditionally onisExport:<div className="flex w-full flex-1 flex-col items-center"> + {isExport && ( + <style>{`.legend--layer__item .panel--header__action { display: none !important; }`}</style> + )} {layers🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/kepler/src/components/CustomMapLegend.tsx` at line 149, The inline <style> injection in LayerLegendItem is duplicated per layer and unconditionally hides panel header actions; remove the per-item injection from the LayerLegendItem component and instead inject the rule `.legend--layer__item .panel--header__action { display: none !important; }` once at the MapLegend render root (or in a module/global CSS or createGlobalStyle), and only apply it when isExport is true so the rule is guarded; update MapLegend to render that single conditional style (or import the CSS) and ensure LayerLegendItem no longer inserts any <style> tags.
140-142: Redundant guard — already covered by thelayers.filter(...)upstream.After the correct fix to line 83 (
!isExport || layer.config.isVisible), invisible layers in export mode will never reachLayerLegendItem, making this early-return unreachable. It is also currently dead code given the unconditional filter on line 83. This block can be removed once the upstream filter is corrected.- if (isExport && !layer.config.isVisible) { - return null; - }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/kepler/src/components/CustomMapLegend.tsx` around lines 140 - 142, Remove the redundant early-return guard in LayerLegendItem that checks "if (isExport && !layer.config.isVisible) return null;" because invisible layers in export mode are already filtered out upstream (after fixing the upstream filter to "!isExport || layer.config.isVisible"), so this block is dead code; locate the conditional in the LayerLegendItem component and delete the entire if-statement to simplify the component and avoid unreachable code.
109-109:isExpandedinitialised fromlayer.config.isVisible— orthogonal concerns.Expand/collapse state and layer visibility are independent. Initialising
isExpandedfromlayer.config.isVisiblealso means that once the unconditional filter (line 83) is fixed and invisible layers re-appear in normal mode, they will all start collapsed rather than in a consistent default state.true(start expanded) is the conventional default for legend items.- const [isExpanded, setIsExpanded] = useState(layer.config.isVisible); + const [isExpanded, setIsExpanded] = useState(true);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/kepler/src/components/CustomMapLegend.tsx` at line 109, The expand/collapse state is incorrectly initialized from layer.config.isVisible (in CustomMapLegend using const [isExpanded, setIsExpanded] = useState(...)), coupling visibility with expansion; change the initializer to a consistent default (useState(true)) so legend items start expanded, and leave layer.config.isVisible solely for rendering visibility—update any code that relied on the previous coupling to use the separate isExpanded state instead of layer.config.isVisible where appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/kepler/src/components/CustomMapLegend.tsx`:
- Around line 82-94: In CustomMapLegend, the unconditional .filter((layer) =>
layer.config.isVisible) removes non-visible layers even in interactive map mode;
update the rendering so the filter is applied only for export mode (isExport) —
e.g., compute the list to render as isExport ? layers.filter(l =>
l.config.isVisible) : layers — and pass each layer into LayerLegendItem (retain
containerW/isExport and {...restProps}) so the legend can display invisible
layers and allow toggling visibility when not exporting; locate the map/list
rendering around the layers.map(...) block and adjust accordingly.
---
Nitpick comments:
In `@packages/kepler/src/components/CustomMapLegend.tsx`:
- Line 149: The inline <style> injection in LayerLegendItem is duplicated per
layer and unconditionally hides panel header actions; remove the per-item
injection from the LayerLegendItem component and instead inject the rule
`.legend--layer__item .panel--header__action { display: none !important; }` once
at the MapLegend render root (or in a module/global CSS or createGlobalStyle),
and only apply it when isExport is true so the rule is guarded; update MapLegend
to render that single conditional style (or import the CSS) and ensure
LayerLegendItem no longer inserts any <style> tags.
- Around line 140-142: Remove the redundant early-return guard in
LayerLegendItem that checks "if (isExport && !layer.config.isVisible) return
null;" because invisible layers in export mode are already filtered out upstream
(after fixing the upstream filter to "!isExport || layer.config.isVisible"), so
this block is dead code; locate the conditional in the LayerLegendItem component
and delete the entire if-statement to simplify the component and avoid
unreachable code.
- Line 109: The expand/collapse state is incorrectly initialized from
layer.config.isVisible (in CustomMapLegend using const [isExpanded,
setIsExpanded] = useState(...)), coupling visibility with expansion; change the
initializer to a consistent default (useState(true)) so legend items start
expanded, and leave layer.config.isVisible solely for rendering
visibility—update any code that relied on the previous coupling to use the
separate isExpanded state instead of layer.config.isVisible where appropriate.
There was a problem hiding this comment.
Pull request overview
This PR updates the custom map legend panel to improve the appearance of exported images by cleaning up the legend UI. The changes conditionally hide interactive elements and filter layers based on visibility when exporting maps.
Changes:
- Added
isExportprop to control legend behavior during image export - Conditionally hide the legend header (title and close button) in export mode
- Remove layer visibility toggle button from legend items
- Filter layers to only show visible ones in the legend
Comments suppressed due to low confidence (1)
packages/kepler/src/components/CustomMapLegend.tsx:170
- Consider hiding the expand/collapse chevron button in export mode for a cleaner appearance in the exported image. Since the export is a static image, interactive controls like this button don't serve a functional purpose. If hidden, ensure layers are expanded by default in export mode so the legend content is visible. This could be done by adding
{!isExport && <Button ...>}around the button and ensuringisExpandedis set to true whenisExportis true.
<Button
className="h-7 w-7"
variant="ghost"
size="icon"
onClick={handleToggleExpanded}
>
{isExpanded ? (
<ChevronDownIcon className="h-4 w-4" />
) : (
<ChevronRightIcon className="h-4 w-4" />
)}
</Button>
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ); | ||
| })} | ||
| {layers | ||
| .filter((layer) => layer.config.isVisible) |
There was a problem hiding this comment.
The visibility filter should be applied conditionally based on isExport. Currently, all invisible layers are filtered out even in normal (non-export) mode. This changes the existing behavior where users could see and interact with invisible layers in the legend. The filter should only apply when isExport is true: .filter((layer) => !isExport || layer.config.isVisible)
| .filter((layer) => layer.config.isVisible) | |
| .filter((layer) => !isExport || layer.config.isVisible) |
There was a problem hiding this comment.
Hidden layers should not show in the legend panel irregardless of the isExport
Related to this slack thread:
https://foursquare.slack.com/archives/C08ANNK76FM/p1771564223125299?thread_ts=1771543581.796869&cid=C08ANNK76FM
tasks:
Screenshot of the UI and exported image:


Summary by CodeRabbit