Skip to content

Pins: Add sidebar for pinned map cards#683

Open
RutanshS wants to merge 2 commits into
masterfrom
pinned-cards
Open

Pins: Add sidebar for pinned map cards#683
RutanshS wants to merge 2 commits into
masterfrom
pinned-cards

Conversation

@RutanshS

Copy link
Copy Markdown

Fixes #669

Summary: Replaces the floating map cards with a sidebar panel. Pinned entities appear in a scrollable sidebar on the left side of the map. Hovering on the said cards highlights the corresponding map centroids or territories.

Changes

  • User experience
    • Clicking a map centroid or territory pins/unpins it. (Toggle)
    • Pinned entities appear in a sidebar panel instead of floating over the map.
    • Hovering a sidebar card highlights the corresponding centroid or territory on the map.
    • Clicking anywhere else on the map no longer closes all cards.
  • Logical changes
    • EntityMap: Replaced FloatingCard state and map positioning logic with the MapSidebar Component and uses the pinnedEntities derived from the URL.
    • MapCentroids / MapTerritories: Added hoveredId and pinnedIds for visual feedback on pinned entities. Fixed pinCard typing.
  • Refactors
    • Renamed openCard to pinCard and closeCard to unpinCard to match the actual behavior.
    • Removed the mapScale, updateMapScale and handleZoomIn function wrappers that existed only for positioning floating cards.

Out of scope/Future work: Unit Tests, Clear All Button in sidebar, Maximum Pin Limit (Maybe).

Screenshots

Pinned Single Card

Pinned Single Card

Pinned Multiple Cards

Pinned Multiple Cards

Pinned Card Hover

Pinned Card Hover

@RutanshS RutanshS requested a review from a team as a code owner June 23, 2026 14:31
@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Pages preview

@RutanshS RutanshS requested a review from conradarcturus June 23, 2026 14:35
@RutanshS

Copy link
Copy Markdown
Author

Follow Up: Theme Relevant Border Colors

Latest commit addresses the feedback that the pinned entity border colors do not appear to be contrasting based on the theme. Fixed it to have different contrasting colors for the pinned centroid or territory borders based on the theme. The chosen colors are from the existing design guideline document. Also restored the map border.

@RutanshS RutanshS self-assigned this Jun 26, 2026

@conradarcturus conradarcturus left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is looking very promising! I have a number of recommendations on styling and triggering events but it's a good start.

@@ -32,17 +26,12 @@ type Props = {
maxWidth?: number;
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maps can be in the main data view panel but also in the Details panel for Languages and Territories (click on a language and scroll to the bottom of the details and you'll see maps). It's weird to have the siderbar in those places because the page width is too small. Can you add a parameter allowSidebar default set to false and in ViewMap.tsx only set it to true.

https://translation-commons.github.io/lang-nav/data?view=Map&pinned=bel&objectID=bel

Image

}}
onClick={() => updatePageParams({ pinned: [] })}
>
<MapSidebar

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

only show this if allowSidebar is true

// Floating cards are derived from the pinned page param so they can be fully restored from the
// URL after a refresh. Each card is positioned at its entity's Robinson centroid.
const floatingCards = useMemo<FloatingCard[]>(() => {
const pinnedEntities = useMemo(() => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Since this is only used in MapSidebar I would remove this code from EntityMap and pass drawableEntities to MapSidebar and do this memo there.

Comment on lines +74 to +80
const pinCard = useCallback(
(entity: DrawableData) => {
updateMapScale();
if (pinned.includes(entity.ID)) return;
updatePageParams({ pinned: [...pinned, entity.ID] });
if (pinned.includes(entity.ID)) {
updatePageParams({ pinned: pinned.filter((id) => id !== entity.ID) });
} else {
updatePageParams({ pinned: [...pinned, entity.ID] });
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Given the above changes, let's change this method from always being pinCard to instead be called onClick. If the sidebar is allowed, then update pins, if the sidebar is not allowed then let's just open/or update the details page: updatePageParams({objectID: entity.ID})

position: 'relative',
background: 'var(--color-background)',
}}
onClick={() => updatePageParams({ pinned: [] })}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

yeap, good idea. We no longer need to dismiss all pins with an off-card click.

});
}, [territories, getColor, isTerritoryInList, colorBy, svgLoaded]);
}, [territories, getColor, isTerritoryInList, colorBy, svgLoaded, hoveredId, pinnedIds]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Add a separate useEffect here to update class names.

and to map.css add definitions:

.mapTerritory {
  cursor: pointer;
}

.mapTerritory:hover,
.mapTerritory.hovered {
  filter: brightness(1.2);
  stroke: var(--color-button-primary);
  stroke-width: 2px;
}

.mapTerritory.pinned {
  stroke: var(--color-button-primary);
  filter: brightness(1.1);
  stroke-width: 2px;
}
Suggested change
// Manage hovered and pinned states
useEffect(() => {
if (!svgLoaded) return;
forEachTerritory((territory, element) => {
element.classList.add('mapTerritory');
element.classList.remove('hovered');
element.classList.remove('pinned');
element.classList.remove('inList');
if (pinnedIds.includes(territory.ID)) element.classList.add('pinned');
if (hoveredId === territory.ID) element.classList.add('hovered');
});
}, [svgLoaded, hoveredId, pinnedIds]);

Comment on lines 86 to 96
(territory: TerritoryData, element: SVGElement) => (ev: MouseEvent) => {
showHoverCard(
<div>
<strong>{territory.nameDisplay}</strong>
<div style={{ color: 'var(--color-text-secondary)' }}>Click for more</div>
</div>,
ev.clientX,
ev.clientY,
);

element.style.opacity = '0.7';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's remove the manual styling here and use CSS instead as mentioned before. Also changing from opacity to brightness.

Suggested change
(territory: TerritoryData) => (ev: MouseEvent) => {
showHoverCard(
<div>
<strong>{territory.nameDisplay}</strong>
<div style={{ color: 'var(--color-text-secondary)' }}>Click for more</div>
</div>,
ev.clientX,
ev.clientY,
);

Comment on lines 101 to 107
const buildOnMouseLeave = useCallback(
(element: SVGElement) => () => {
onMouseLeaveTriggeringElement();
element.style.opacity = '1';
},
[onMouseLeaveTriggeringElement],
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We can simplify this now that we have css

Comment on lines +31 to +35
borderRight: '1px solid var(--color-text-secondary)',
display: 'flex',
flexDirection: 'column',
gap: '1em',
padding: '1em',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't think the border is necessary

Suggested change
borderRight: '1px solid var(--color-text-secondary)',
display: 'flex',
flexDirection: 'column',
gap: '1em',
padding: '1em',
display: 'flex',
flexDirection: 'column',
gap: '1em',
padding: '1em',

setHoveredId: (id: string | null) => void;
};

const MapSidebar: React.FC<Props> = ({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I have other ideas on the appearance of this but I don't want to tackle too many changes at a time ;) So for now I'll call this good and we'll iterate on it after the existing feedback.

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.

Pins: Reconsider Map Pins

2 participants