Skip to content

Commit d6f7b72

Browse files
eurunuelaclaude
andcommitted
Modernize navbar and improve UX
- Add Notion-style navbar with favicon logo, pill tabs, and modern buttons - Implement click-outside-to-close for popup modals - Fix output directory path extraction from tedana log - Simplify tab component with pill-style selected state - Add version badge (v2.0) next to logo 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 95bb27f commit d6f7b72

6 files changed

Lines changed: 251 additions & 119 deletions

File tree

claude-progress.txt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,50 @@ const blue2white = {
575575

576576
---
577577

578+
## Session 3.4 - Modern Navbar and UX Polish (2025-12-14)
579+
580+
### Goals
581+
- Modernize the top navbar with Notion-style design
582+
- Add click-outside-to-close functionality for popups
583+
- Add favicon as logo in navbar
584+
- Fix data path extraction for Info tab
585+
586+
### Changes Made
587+
588+
#### Modern Navbar (index.js)
589+
- **Left section**: Favicon logo (28x28px) + "Rica" text + version badge (v2.0)
590+
- **Center section**: Pill-style tab navigation with gray background container
591+
- Selected tab has white background with subtle shadow
592+
- Smooth hover effects on unselected tabs
593+
- **Right section**:
594+
- "New" button with border and plus icon
595+
- Icon-only "About" button (cleaner look)
596+
- Clean white background with subtle shadow
597+
- Better spacing and typography
598+
599+
#### Tab Styling (TabFunctions.js)
600+
- Removed underline indicator animation
601+
- Changed to pill-style selected state (white background, rounded corners)
602+
- Simplified component by removing unused refs and effects
603+
604+
#### Click-Outside-to-Close (IntroPopUp.js, AboutPopUp.js)
605+
- Added onClick handler on backdrop to close popup
606+
- Inner card uses stopPropagation() to prevent closing when clicking inside
607+
- Same behavior as clicking the X button
608+
609+
#### Path Extraction Fix (IntroPopUp.js)
610+
- Fixed regex to properly extract output directory from tedana log
611+
- Now searches for "Using output directory:" and extracts full path
612+
- Handles paths with colons correctly
613+
614+
### Files Modified
615+
- `src/index.js` - Modern navbar layout with favicon
616+
- `src/TabFunctions.js` - Pill-style tabs, removed underline
617+
- `src/PopUps/IntroPopUp.js` - Click-outside-to-close, path extraction fix
618+
- `src/PopUps/AboutPopUp.js` - Click-outside-to-close
619+
620+
---
621+
578622
## Template for Future Sessions
579623

580624
```

features.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,46 @@
386386
"Verify the classification badge in the table updates"
387387
],
388388
"passes": true
389+
},
390+
{
391+
"id": "modern_navbar",
392+
"category": "ui",
393+
"description": "Display modern Notion-style navbar with logo, pill tabs, and action buttons",
394+
"steps": [
395+
"Load data and view the navbar",
396+
"Verify favicon logo appears next to 'Rica' text",
397+
"Verify version badge (v2.0) is displayed",
398+
"Verify tabs are in pill-style container with gray background",
399+
"Verify selected tab has white background",
400+
"Verify New and About buttons have modern styling"
401+
],
402+
"passes": true
403+
},
404+
{
405+
"id": "click_outside_close_popup",
406+
"category": "ui",
407+
"description": "Close popups by clicking outside the modal card",
408+
"steps": [
409+
"Click 'New' button to open intro popup",
410+
"Click outside the white card (on the gray backdrop)",
411+
"Verify popup closes",
412+
"Click 'About' button to open about popup",
413+
"Click outside the white card",
414+
"Verify popup closes"
415+
],
416+
"passes": true
417+
},
418+
{
419+
"id": "display_data_path",
420+
"category": "visualization",
421+
"description": "Display the correct output directory path in the Info tab",
422+
"steps": [
423+
"Load a tedana folder",
424+
"Navigate to the 'Info' tab",
425+
"Verify the output directory path is displayed in the blue badge",
426+
"Verify the path matches the 'Using output directory' from tedana log"
427+
],
428+
"passes": true
389429
}
390430
]
391431
}

src/PopUps/AboutPopUp.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@ import { faGithub } from "@fortawesome/free-brands-svg-icons";
44

55
function AboutPopup({ closePopup }) {
66
return (
7-
<div className="fixed z-10 flex items-center justify-center w-full h-full bg-gray-500 bg-opacity-50 backdrop-blur-sm">
8-
<div className="absolute z-20 w-1/3 px-16 py-10 m-auto bg-white h-fit rounded-xl drop-shadow-2xl animate-fadeIn">
7+
<div
8+
className="fixed z-10 flex items-center justify-center w-full h-full bg-gray-500 bg-opacity-50 backdrop-blur-sm"
9+
onClick={closePopup}
10+
>
11+
<div
12+
className="absolute z-20 w-1/3 px-16 py-10 m-auto bg-white h-fit rounded-xl drop-shadow-2xl animate-fadeIn"
13+
onClick={(e) => e.stopPropagation()}
14+
>
915
<button
1016
onClick={closePopup}
1117
type="button"

src/PopUps/IntroPopUp.js

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,17 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading }) {
136136
// Dataset path
137137
if (filename.startsWith("tedana_20") && filename.endsWith(".tsv")) {
138138
const text = await readFileAsText(file);
139-
const parsed = Papa.parse(text, {
140-
header: false,
141-
skipEmptyLines: true,
142-
});
143-
const row = parsed.data[0];
144-
const pathStr = row[row.length - 1];
145-
dirPath = pathStr.includes(":") ? pathStr.split(":")[1].trim() : pathStr;
139+
// Look for the line containing "Using output directory:"
140+
const lines = text.split("\n");
141+
for (const line of lines) {
142+
if (line.includes("Using output directory:")) {
143+
const match = line.match(/Using output directory:\s*(.+)/);
144+
if (match) {
145+
dirPath = match[1].trim();
146+
break;
147+
}
148+
}
149+
}
146150
processed++;
147151
setLoadingProgress((prev) => ({ ...prev, current: processed }));
148152
}
@@ -198,8 +202,14 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading }) {
198202
);
199203

200204
return (
201-
<div className="fixed z-10 flex items-center justify-center w-full h-full bg-gray-500 bg-opacity-50 backdrop-blur-sm">
202-
<div className="absolute z-20 w-1/3 px-16 py-10 m-auto bg-white h-fit rounded-xl drop-shadow-2xl transition-all duration-300">
205+
<div
206+
className="fixed z-10 flex items-center justify-center w-full h-full bg-gray-500 bg-opacity-50 backdrop-blur-sm"
207+
onClick={closePopup}
208+
>
209+
<div
210+
className="absolute z-20 w-1/3 px-16 py-10 m-auto bg-white h-fit rounded-xl drop-shadow-2xl transition-all duration-300"
211+
onClick={(e) => e.stopPropagation()}
212+
>
203213
<button
204214
onClick={closePopup}
205215
type="button"

src/TabFunctions.js

Lines changed: 28 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,20 @@
1-
import React, { useRef, useState, useLayoutEffect, useCallback } from "react";
1+
import React, { useRef, useCallback } from "react";
22
import { TabsProvider, useTabsContext } from "./TabComponents";
33

4-
const HORIZONTAL_PADDING = 16;
5-
64
function AnimatedTabs({ children, defaultIndex = 0, ...rest }) {
7-
const [activeRect, setActiveRect] = useState(null);
8-
const containerRef = useRef(null);
9-
const [containerRect, setContainerRect] = useState(null);
10-
11-
// Update container rect on mount and resize
12-
useLayoutEffect(() => {
13-
const updateRect = () => {
14-
if (containerRef.current) {
15-
setContainerRect(containerRef.current.getBoundingClientRect());
16-
}
17-
};
18-
19-
updateRect();
20-
window.addEventListener("resize", updateRect);
21-
return () => window.removeEventListener("resize", updateRect);
22-
}, []);
23-
245
return (
256
<TabsProvider defaultIndex={defaultIndex}>
26-
<TabsContextBridge setActiveRect={setActiveRect}>
27-
<div
28-
ref={containerRef}
29-
style={{ ...rest.style, position: "relative" }}
30-
{...rest}
31-
>
32-
{/* Animated underline indicator */}
33-
<div
34-
className="absolute bg-sky-500"
35-
style={{
36-
height: 4,
37-
transition: "all 300ms ease",
38-
left: activeRect && containerRect
39-
? activeRect.left - containerRect.left + HORIZONTAL_PADDING
40-
: 0,
41-
top: activeRect && containerRect
42-
? activeRect.bottom - containerRect.top - 3
43-
: 0,
44-
width: activeRect ? activeRect.width - HORIZONTAL_PADDING * 1.5 : 0,
45-
opacity: activeRect ? 1 : 0,
46-
}}
47-
/>
48-
{children}
49-
</div>
50-
</TabsContextBridge>
7+
<div style={{ ...rest.style }} {...rest}>
8+
{children}
9+
</div>
5110
</TabsProvider>
5211
);
5312
}
5413

55-
// Helper component to access context and pass setActiveRect
56-
function TabsContextBridge({ children, setActiveRect }) {
57-
return (
58-
<ActiveRectContext.Provider value={setActiveRect}>
59-
{children}
60-
</ActiveRectContext.Provider>
61-
);
62-
}
63-
64-
const ActiveRectContext = React.createContext(null);
65-
6614
function AnimatedTab({ index, children, style, ...props }) {
6715
const { selectedIndex, selectTab } = useTabsContext();
6816
const isSelected = selectedIndex === index;
6917
const tabRef = useRef(null);
70-
const setActiveRect = React.useContext(ActiveRectContext);
71-
72-
// Update active rect when this tab becomes selected
73-
useLayoutEffect(() => {
74-
if (isSelected && tabRef.current) {
75-
setActiveRect(tabRef.current.getBoundingClientRect());
76-
}
77-
}, [isSelected, setActiveRect]);
78-
79-
// Also update on resize
80-
useLayoutEffect(() => {
81-
if (!isSelected) return;
82-
83-
const updateRect = () => {
84-
if (tabRef.current) {
85-
setActiveRect(tabRef.current.getBoundingClientRect());
86-
}
87-
};
88-
89-
window.addEventListener("resize", updateRect);
90-
return () => window.removeEventListener("resize", updateRect);
91-
}, [isSelected, setActiveRect]);
9218

9319
const handleClick = useCallback(() => {
9420
selectTab(index);
@@ -99,15 +25,33 @@ function AnimatedTab({ index, children, style, ...props }) {
9925
ref={tabRef}
10026
role="tab"
10127
aria-selected={isSelected}
102-
className={`text-gray-500 hover:cursor-pointer focus:outline-none transition-colors duration-200 ${
103-
isSelected ? "text-gray-900" : ""
104-
}`}
10528
onClick={handleClick}
10629
style={{
107-
...style,
108-
padding: `8px ${HORIZONTAL_PADDING}px`,
109-
background: "white",
30+
display: "flex",
31+
alignItems: "center",
32+
padding: "6px 12px",
33+
fontSize: "13px",
34+
fontWeight: isSelected ? "600" : "500",
35+
color: isSelected ? "#1f2937" : "#6b7280",
36+
backgroundColor: isSelected ? "#ffffff" : "transparent",
11037
border: "none",
38+
borderRadius: "6px",
39+
cursor: "pointer",
40+
transition: "all 0.15s ease",
41+
boxShadow: isSelected ? "0 1px 2px rgba(0, 0, 0, 0.05)" : "none",
42+
...style,
43+
}}
44+
onMouseEnter={(e) => {
45+
if (!isSelected) {
46+
e.currentTarget.style.color = "#374151";
47+
e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.5)";
48+
}
49+
}}
50+
onMouseLeave={(e) => {
51+
if (!isSelected) {
52+
e.currentTarget.style.color = "#6b7280";
53+
e.currentTarget.style.backgroundColor = "transparent";
54+
}
11155
}}
11256
{...props}
11357
>

0 commit comments

Comments
 (0)