55 BeakerIcon ,
66 BellAlertIcon ,
77 ChartBarIcon ,
8- ChartBarSquareIcon ,
98 ChevronRightIcon ,
109 ClockIcon ,
1110 Cog8ToothIcon ,
@@ -26,7 +25,8 @@ import {
2625import { DialogClose } from "@radix-ui/react-dialog" ;
2726import { Form , Link , useFetcher , useNavigation } from "@remix-run/react" ;
2827import { LayoutGroup , motion } from "framer-motion" ;
29- import { useCallback , useEffect , useRef , useState , type ReactNode } from "react" ;
28+ import { LineChartIcon } from "lucide-react" ;
29+ import { type ReactNode , useCallback , useEffect , useRef , useState } from "react" ;
3030import simplur from "simplur" ;
3131import { ConcurrencyIcon } from "~/assets/icons/ConcurrencyIcon" ;
3232import { DropdownIcon } from "~/assets/icons/DropdownIcon" ;
@@ -36,30 +36,20 @@ import { LogsIcon } from "~/assets/icons/LogsIcon";
3636import { RunsIconExtraSmall } from "~/assets/icons/RunsIcon" ;
3737import { TaskIconSmall } from "~/assets/icons/TaskIcon" ;
3838import { WaitpointTokenIcon } from "~/assets/icons/WaitpointTokenIcon" ;
39+ import { Feedback } from "~/components/Feedback" ;
3940import { Avatar } from "~/components/primitives/Avatar" ;
40- import { type MatchedEnvironment , useEnvironment } from "~/hooks/useEnvironment" ;
41+ import { type MatchedEnvironment } from "~/hooks/useEnvironment" ;
4142import { useFeatureFlags } from "~/hooks/useFeatureFlags" ;
4243import { useFeatures } from "~/hooks/useFeatures" ;
4344import {
4445 type MatchedOrganization ,
4546 useCustomDashboards ,
4647 useDashboardLimits ,
4748} from "~/hooks/useOrganizations" ;
48- import { type MatchedProject , useProject } from "~/hooks/useProject" ;
49- import { useHasAdminAccess } from "~/hooks/useUser" ;
49+ import { type MatchedProject } from "~/hooks/useProject" ;
5050import { useShortcutKeys } from "~/hooks/useShortcutKeys" ;
51- import { ShortcutKey } from "../primitives/ShortcutKey " ;
51+ import { useHasAdminAccess } from "~/hooks/useUser " ;
5252import { type UserWithDashboardPreferences } from "~/models/user.server" ;
53- import { type SideMenuSectionId } from "./sideMenuTypes" ;
54-
55- /** Get the collapsed state for a specific side menu section from user preferences */
56- function getSectionCollapsed (
57- sideMenu : { collapsedSections ?: Record < string , boolean > } | undefined ,
58- sectionId : SideMenuSectionId
59- ) : boolean {
60- return sideMenu ?. collapsedSections ?. [ sectionId ] ?? false ;
61- }
62- import { Feedback } from "~/components/Feedback" ;
6353import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route" ;
6454import { type FeedbackType } from "~/routes/resources.feedback" ;
6555import { IncidentStatusPanel } from "~/routes/resources.incidents" ;
@@ -98,7 +88,7 @@ import {
9888 v3UsagePath ,
9989 v3WaitpointTokensPath ,
10090} from "~/utils/pathBuilder" ;
101- import { AlphaBadge , BetaBadge } from "../AlphaBadge" ;
91+ import { AlphaBadge } from "../AlphaBadge" ;
10292import { AskAI } from "../AskAI" ;
10393import { FreePlanUsage } from "../billing/FreePlanUsage" ;
10494import { ConnectionIcon , DevPresencePanel , useDevPresence } from "../DevPresence" ;
@@ -118,6 +108,7 @@ import { InputGroup } from "../primitives/InputGroup";
118108import { Label } from "../primitives/Label" ;
119109import { Paragraph } from "../primitives/Paragraph" ;
120110import { Popover , PopoverContent , PopoverMenuItem , PopoverTrigger } from "../primitives/Popover" ;
111+ import { ShortcutKey } from "../primitives/ShortcutKey" ;
121112import { TextLink } from "../primitives/TextLink" ;
122113import {
123114 SimpleTooltip ,
@@ -133,7 +124,15 @@ import { HelpAndFeedback } from "./HelpAndFeedbackPopover";
133124import { SideMenuHeader } from "./SideMenuHeader" ;
134125import { SideMenuItem } from "./SideMenuItem" ;
135126import { SideMenuSection } from "./SideMenuSection" ;
136- import { BarChart2Icon , LineChartIcon } from "lucide-react" ;
127+ import { type SideMenuSectionId } from "./sideMenuTypes" ;
128+
129+ /** Get the collapsed state for a specific side menu section from user preferences */
130+ function getSectionCollapsed (
131+ sideMenu : { collapsedSections ?: Record < string , boolean > } | undefined ,
132+ sectionId : SideMenuSectionId
133+ ) : boolean {
134+ return sideMenu ?. collapsedSections ?. [ sectionId ] ?? false ;
135+ }
137136
138137type SideMenuUser = Pick <
139138 UserWithDashboardPreferences ,
@@ -497,7 +496,7 @@ export function SideMenu({
497496 />
498497 < SideMenuItem
499498 name = "Metrics"
500- icon = { BarChart2Icon }
499+ icon = { ChartBarIcon }
501500 activeIconColor = "text-metrics"
502501 inactiveIconColor = "text-metrics"
503502 to = { v3BuiltInDashboardPath ( organization , project , environment , "overview" ) }
@@ -512,17 +511,26 @@ export function SideMenu({
512511 />
513512 }
514513 />
515- { customDashboards . map ( ( dashboard ) => (
516- < SideMenuItem
517- key = { dashboard . friendlyId }
518- name = { dashboard . title }
519- icon = { LineChartIcon }
520- activeIconColor = "text-customDashboards"
521- inactiveIconColor = "text-customDashboards"
522- to = { v3CustomDashboardPath ( organization , project , environment , dashboard ) }
523- isCollapsed = { isCollapsed }
524- />
525- ) ) }
514+ { customDashboards . map ( ( dashboard , index ) => {
515+ const isLast = index === customDashboards . length - 1 ;
516+ return (
517+ < SideMenuItem
518+ key = { dashboard . friendlyId }
519+ name = { dashboard . title }
520+ icon = {
521+ isCollapsed
522+ ? LineChartIcon
523+ : isLast
524+ ? TreeConnectorEnd
525+ : TreeConnectorBranch
526+ }
527+ activeIconColor = "text-customDashboards"
528+ inactiveIconColor = "text-customDashboards"
529+ to = { v3CustomDashboardPath ( organization , project , environment , dashboard ) }
530+ isCollapsed = { isCollapsed }
531+ />
532+ ) ;
533+ } ) }
526534 </ SideMenuSection >
527535 ) }
528536
@@ -1224,6 +1232,26 @@ function AnimatedChevron({
12241232 ) ;
12251233}
12261234
1235+ // Tree connector icons for sub-items. The SVG viewBox is 20x20 matching the size-5 icon area.
1236+ // Lines extend to y=-6 and y=26 to fill the full 32px row height (6px gap above/below the 20px icon).
1237+ function TreeConnectorBranch ( { className } : { className ?: string } ) {
1238+ return (
1239+ < svg className = { cn ( "overflow-visible" , className ) } viewBox = "0 0 20 20" fill = "none" >
1240+ < line x1 = "10" y1 = "-6" x2 = "10" y2 = "26" stroke = "currentColor" strokeWidth = "1" />
1241+ < line x1 = "10" y1 = "10" x2 = "20" y2 = "10" stroke = "currentColor" strokeWidth = "1" />
1242+ </ svg >
1243+ ) ;
1244+ }
1245+
1246+ function TreeConnectorEnd ( { className } : { className ?: string } ) {
1247+ return (
1248+ < svg className = { cn ( "overflow-visible" , className ) } viewBox = "0 0 20 20" fill = "none" >
1249+ < line x1 = "10" y1 = "-6" x2 = "10" y2 = "10" stroke = "currentColor" strokeWidth = "1" />
1250+ < line x1 = "10" y1 = "10" x2 = "20" y2 = "10" stroke = "currentColor" strokeWidth = "1" />
1251+ </ svg >
1252+ ) ;
1253+ }
1254+
12271255function CollapseToggle ( { isCollapsed, onToggle } : { isCollapsed : boolean ; onToggle : ( ) => void } ) {
12281256 const [ isHovering , setIsHovering ] = useState ( false ) ;
12291257
0 commit comments