Skip to content

Commit 90e0b16

Browse files
authored
Merge pull request #267 from commonknowledge/copilot/update-layers-panel-headings
Update layers panel to match design: full opacity, lucide icons, hover interactions
2 parents 5b611bc + a8ac82b commit 90e0b16

File tree

14 files changed

+148
-107
lines changed

14 files changed

+148
-107
lines changed

src/app/map/[id]/components/LayerTypeIcon.tsx

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,16 @@
1-
import { Grid3x3Icon } from "lucide-react";
2-
import { mapColors } from "@/app/map/[id]/styles";
1+
import {
2+
LayoutDashboardIcon,
3+
MapPinIcon,
4+
SquareIcon,
5+
UsersIcon,
6+
} from "lucide-react";
37
import { cn } from "@/shadcn/utils";
48
import { LayerType } from "@/types";
59

6-
function DotIcon({ color, size }: { color: string; size: number }) {
7-
return (
8-
<div
9-
className={` w-${size} h-${size} rounded-full`}
10-
style={{ backgroundColor: color }}
11-
></div>
12-
);
13-
}
14-
15-
function TurfIcon({ size }: { size: number }) {
16-
return (
17-
<div
18-
className={` w-${size} h-${size} rounded-xs border-2`}
19-
style={{
20-
backgroundColor: mapColors.areas.color + "50",
21-
borderColor: mapColors.areas.color,
22-
}}
23-
></div>
24-
);
25-
}
26-
2710
export default function LayerTypeIcon({
2811
type,
2912
className = "",
30-
size = 3,
13+
size = 16,
3114
}: {
3215
type: LayerType | undefined;
3316
className?: string;
@@ -36,13 +19,13 @@ export default function LayerTypeIcon({
3619
const getIcon = () => {
3720
switch (type) {
3821
case LayerType.Member:
39-
return <DotIcon color={mapColors.member.color} size={size} />;
22+
return <UsersIcon size={size} />;
4023
case LayerType.Marker:
41-
return <DotIcon color={mapColors.markers.color} size={size} />;
24+
return <MapPinIcon size={size} />;
4225
case LayerType.Turf:
43-
return <TurfIcon size={size} />;
26+
return <SquareIcon size={size} />;
4427
case LayerType.Boundary:
45-
return <Grid3x3Icon size={size * 4} />;
28+
return <LayoutDashboardIcon size={size} />;
4629
default:
4730
return <></>;
4831
}

src/app/map/[id]/components/controls/BoundariesControl/BoundariesControl.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default function BoundariesControl() {
3232
</LayerHeader>
3333

3434
{expanded && (
35-
<div className="space-y-2 py-2">
35+
<div className="px-4 pt-2 pb-3 space-y-2">
3636
{/* Controls removed from here 2025-12-08. */}
3737
{/* Potentially could be restored. Remove if still not restored by 2025-03-01 */}
3838
{/* <ShapeSelector />

src/app/map/[id]/components/controls/ControlContextMenuContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default function ControlContextMenuContent({
1212
<ContextMenuContent>
1313
<ContextMenuItem onClick={onEdit}>
1414
<PencilIcon size={12} />
15-
Edit
15+
Rename
1616
</ContextMenuItem>
1717
<ContextMenuItem onClick={onDelete}>
1818
<TrashIcon size={12} />
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { PencilIcon, TrashIcon } from "lucide-react";
2+
import { useRef, useState } from "react";
3+
import {
4+
DropdownMenu,
5+
DropdownMenuContent,
6+
DropdownMenuItem,
7+
DropdownMenuTrigger,
8+
} from "@/shadcn/ui/dropdown-menu";
9+
10+
export default function ControlHoverMenu({
11+
children,
12+
onEdit,
13+
onDelete,
14+
}: {
15+
children: React.ReactNode;
16+
onEdit: () => void;
17+
onDelete: () => void;
18+
}) {
19+
const [open, setOpen] = useState(false);
20+
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
21+
22+
const handleMouseEnter = () => {
23+
if (timeoutRef.current) {
24+
clearTimeout(timeoutRef.current);
25+
timeoutRef.current = null;
26+
}
27+
setOpen(true);
28+
};
29+
30+
const handleMouseLeave = () => {
31+
timeoutRef.current = setTimeout(() => {
32+
setOpen(false);
33+
}, 200);
34+
};
35+
36+
return (
37+
<div
38+
className="relative w-full"
39+
onMouseEnter={handleMouseEnter}
40+
onMouseLeave={handleMouseLeave}
41+
>
42+
<DropdownMenu open={open} modal={false}>
43+
<DropdownMenuTrigger asChild>
44+
<div className="w-full">{children}</div>
45+
</DropdownMenuTrigger>
46+
<DropdownMenuContent
47+
align="start"
48+
side="right"
49+
sideOffset={38}
50+
alignOffset={-4}
51+
onMouseEnter={handleMouseEnter}
52+
onMouseLeave={handleMouseLeave}
53+
>
54+
<DropdownMenuItem onClick={onEdit}>
55+
<PencilIcon size={12} />
56+
Rename
57+
</DropdownMenuItem>
58+
<DropdownMenuItem onClick={onDelete} variant="destructive">
59+
<TrashIcon size={12} />
60+
Delete
61+
</DropdownMenuItem>
62+
</DropdownMenuContent>
63+
</DropdownMenu>
64+
</div>
65+
);
66+
}

src/app/map/[id]/components/controls/ControlWrapper.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,24 @@ export default function ControlWrapper({
3333
return (
3434
<div
3535
className={cn(
36-
"relative flex gap-1 text-sm",
36+
"group relative flex gap-1 text-sm",
3737
isVisible ? "opacity-100" : "opacity-70",
3838
)}
3939
>
40+
<div
41+
className="absolute top-0 left-0 h-full w-1 shrink-0 rounded-xs"
42+
style={{ background: getLayerColor() }}
43+
></div>
44+
45+
<div className="grow pl-3">{children}</div>
46+
4047
<button
41-
className="shrink-0 bg-neutral-100 hover:bg-neutral-200 text-neutral-500 rounded px-0.5 py-2 flex items-center justify-center self-stretch w-8 mr-2 cursor-pointer"
48+
className="shrink-0 opacity-0 group-hover:opacity-100 transition-opacity bg-neutral-100 hover:bg-neutral-200 text-neutral-500 rounded p-1.5 flex items-center justify-center cursor-pointer"
4249
aria-label={`Toggle ${name} visibility`}
4350
onClick={onVisibilityToggle}
4451
>
4552
{isVisible ? <EyeIcon size={16} /> : <EyeOffIcon size={16} />}
4653
</button>
47-
48-
<div
49-
className="absolute top-0 left-9 h-full w-1 shrink-0 rounded-xs"
50-
style={{ background: getLayerColor() }}
51-
></div>
52-
53-
<div className="grow">{children}</div>
5454
</div>
5555
);
5656
}

src/app/map/[id]/components/controls/LayerControlWrapper.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@ export default function LayerControlWrapper({
55
}: {
66
children: ReactNode;
77
}) {
8-
return <div className="p-2 rounded-lg border bg-white">{children}</div>;
8+
return (
9+
<div className="border-b border-neutral-200 last:border-b-0">
10+
{children}
11+
</div>
12+
);
913
}

src/app/map/[id]/components/controls/LayerHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default function LayerHeader({
3737
};
3838

3939
return (
40-
<div className="flex items-center justify-between relative">
40+
<div className="flex items-center justify-between relative px-4 py-3">
4141
<div className="group flex items-center gap-1">
4242
<button
4343
onClick={() => setExpanded(!expanded)}

src/app/map/[id]/components/controls/MarkersControl/MarkersControl.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,11 @@ export default function MarkersControl() {
154154
<PlusIcon size={16} />
155155
</IconButtonWithTooltip>
156156
</LayerHeader>
157-
{expanded && <MarkersList />}
157+
{expanded && (
158+
<div className="px-4 pb-3">
159+
<MarkersList />
160+
</div>
161+
)}
158162
</LayerControlWrapper>
159163
);
160164
}

src/app/map/[id]/components/controls/MarkersControl/SortableFolderItem.tsx

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
verticalListSortingStrategy,
66
} from "@dnd-kit/sortable";
77
import { CSS } from "@dnd-kit/utilities";
8-
import { ContextMenuTrigger } from "@radix-ui/react-context-menu";
98
import {
109
CornerDownRightIcon,
1110
Folder as FolderClosed,
@@ -14,13 +13,12 @@ import {
1413
import { useMemo, useState } from "react";
1514

1615
import { sortByPositionAndId } from "@/app/map/[id]/utils";
17-
import { ContextMenu } from "@/shadcn/ui/context-menu";
1816
import { cn } from "@/shadcn/utils";
1917
import { LayerType } from "@/types";
2018
import { useFolderMutations } from "../../../hooks/useFolders";
2119
import { usePlacedMarkerState } from "../../../hooks/usePlacedMarkers";
22-
import ControlContextMenuContent from "../ControlContextMenuContent";
2320
import ControlEditForm from "../ControlEditForm";
21+
import ControlHoverMenu from "../ControlHoverMenu";
2422
import ControlWrapper from "../ControlWrapper";
2523
import SortableMarkerItem from "./SortableMarkerItem";
2624
import type { Folder } from "@/server/models/Folder";
@@ -140,32 +138,26 @@ export default function SortableFolderItem({
140138
onSubmit={onSubmit}
141139
/>
142140
) : (
143-
<ContextMenu>
144-
<ContextMenuTrigger asChild>
145-
<button
146-
ref={isDraggingMarker ? setHeaderNodeRef : null}
147-
onClick={() => onClickFolder()}
148-
className={cn(
149-
"flex items-center gap-1 / w-full min-h-full p-1 rounded / transition-colors hover:bg-neutral-100 / text-left cursor-pointer",
150-
isHeaderOver ? "bg-blue-50" : "",
151-
)}
152-
>
153-
{isExpanded ? (
154-
<FolderOpen className="w-4 h-4 text-muted-foreground shrink-0" />
155-
) : (
156-
<FolderClosed className="w-4 h-4 text-muted-foreground shrink-0" />
157-
)}
158-
<span className="text-xs text-muted-foreground transition-transform duration-30 rounded-full bg-neutral-50 px-1">
159-
{sortedMarkers.length}
160-
</span>
161-
{folder.name}
162-
</button>
163-
</ContextMenuTrigger>
164-
<ControlContextMenuContent
165-
onDelete={() => onDelete()}
166-
onEdit={() => onEdit()}
167-
/>
168-
</ContextMenu>
141+
<ControlHoverMenu onDelete={() => onDelete()} onEdit={() => onEdit()}>
142+
<button
143+
ref={isDraggingMarker ? setHeaderNodeRef : null}
144+
onClick={() => onClickFolder()}
145+
className={cn(
146+
"flex items-center gap-1 / w-full min-h-full p-1 rounded / transition-colors hover:bg-neutral-100 / text-left cursor-pointer",
147+
isHeaderOver ? "bg-blue-50" : "",
148+
)}
149+
>
150+
{isExpanded ? (
151+
<FolderOpen className="w-4 h-4 text-muted-foreground shrink-0" />
152+
) : (
153+
<FolderClosed className="w-4 h-4 text-muted-foreground shrink-0" />
154+
)}
155+
<span className="text-xs text-muted-foreground transition-transform duration-30 rounded-full bg-neutral-50 px-1">
156+
{sortedMarkers.length}
157+
</span>
158+
{folder.name}
159+
</button>
160+
</ControlHoverMenu>
169161
)}
170162
</ControlWrapper>
171163

src/app/map/[id]/components/controls/MarkersControl/SortableMarkerItem.tsx

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import { useSortable } from "@dnd-kit/sortable";
22
import { CSS } from "@dnd-kit/utilities";
33
import { useState } from "react";
4-
import { ContextMenu, ContextMenuTrigger } from "@/shadcn/ui/context-menu";
54
import { LayerType } from "@/types";
65
import { useMapRef } from "../../../hooks/useMapCore";
76
import {
87
usePlacedMarkerMutations,
98
usePlacedMarkerState,
109
} from "../../../hooks/usePlacedMarkers";
11-
import ControlContextMenuContent from "../ControlContextMenuContent";
1210
import ControlEditForm from "../ControlEditForm";
11+
import ControlHoverMenu from "../ControlHoverMenu";
1312
import ControlWrapper from "../ControlWrapper";
1413
import type { PlacedMarker } from "@/server/models/PlacedMarker";
1514

@@ -103,20 +102,17 @@ export default function SortableMarkerItem({
103102
onSubmit={onSubmit}
104103
/>
105104
) : (
106-
<ContextMenu>
107-
<ContextMenuTrigger asChild>
108-
<button
109-
className="flex items-center gap-2 / w-full min-h-full p-1 rounded / transition-colors hover:bg-neutral-100 / text-left cursor-pointer"
110-
onClick={() => flyToMarker()}
111-
>
112-
{marker.label}
113-
</button>
114-
</ContextMenuTrigger>
115-
<ControlContextMenuContent
116-
onEdit={() => onEdit()}
117-
onDelete={() => deletePlacedMarker(marker.id)}
118-
/>
119-
</ContextMenu>
105+
<ControlHoverMenu
106+
onEdit={() => onEdit()}
107+
onDelete={() => deletePlacedMarker(marker.id)}
108+
>
109+
<button
110+
className="flex items-center gap-2 / w-full min-h-full p-1 rounded / transition-colors hover:bg-neutral-100 / text-left cursor-pointer"
111+
onClick={() => flyToMarker()}
112+
>
113+
{marker.label}
114+
</button>
115+
</ControlHoverMenu>
120116
)}
121117
</ControlWrapper>
122118
</div>

0 commit comments

Comments
 (0)