feat: dark theme social network graph with arrows + responsive MI bar
Social Network Graph: - Dark/black theme with semi-transparent background - Arrow markers on edges showing connection direction - Color-coded arrows: grey (default), yellow (connected), green (trusted) - Updated header, stats, and icon button colors for dark theme MI (Mycelial Intelligence) Bar: - Responsive width: full width on mobile, percentage on narrow, fixed on desktop - Position: moves to bottom on mobile (above toolbar), stays at top on desktop - Smooth transitions when resizing - Smaller max height on mobile (300px vs 400px) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
27c82246ef
commit
548ec0733e
|
|
@ -62,11 +62,12 @@ const styles = {
|
||||||
gap: '8px',
|
gap: '8px',
|
||||||
},
|
},
|
||||||
panel: {
|
panel: {
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
backgroundColor: 'rgba(20, 20, 25, 0.95)',
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
boxShadow: '0 2px 12px rgba(0, 0, 0, 0.15)',
|
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.4)',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.1)',
|
||||||
},
|
},
|
||||||
panelCollapsed: {
|
panelCollapsed: {
|
||||||
width: '48px',
|
width: '48px',
|
||||||
|
|
@ -81,13 +82,13 @@ const styles = {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
padding: '8px 12px',
|
padding: '8px 12px',
|
||||||
borderBottom: '1px solid rgba(0, 0, 0, 0.1)',
|
borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.02)',
|
backgroundColor: 'rgba(255, 255, 255, 0.03)',
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
color: '#1a1a2e',
|
color: '#e0e0e0',
|
||||||
margin: 0,
|
margin: 0,
|
||||||
},
|
},
|
||||||
headerButtons: {
|
headerButtons: {
|
||||||
|
|
@ -105,8 +106,8 @@ const styles = {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
color: '#666',
|
color: '#a0a0a0',
|
||||||
transition: 'background-color 0.15s',
|
transition: 'background-color 0.15s, color 0.15s',
|
||||||
},
|
},
|
||||||
canvas: {
|
canvas: {
|
||||||
display: 'block',
|
display: 'block',
|
||||||
|
|
@ -131,9 +132,10 @@ const styles = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: '12px',
|
gap: '12px',
|
||||||
padding: '6px 12px',
|
padding: '6px 12px',
|
||||||
borderTop: '1px solid rgba(0, 0, 0, 0.1)',
|
borderTop: '1px solid rgba(255, 255, 255, 0.1)',
|
||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
color: '#666',
|
color: '#888',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
||||||
},
|
},
|
||||||
stat: {
|
stat: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|
@ -214,22 +216,74 @@ export function NetworkGraphMinimap({
|
||||||
// Create container group
|
// Create container group
|
||||||
const g = svg.append('g');
|
const g = svg.append('g');
|
||||||
|
|
||||||
|
// Create arrow marker definitions for edges
|
||||||
|
const defs = svg.append('defs');
|
||||||
|
|
||||||
|
// Arrow marker for regular edges (grey)
|
||||||
|
defs.append('marker')
|
||||||
|
.attr('id', 'arrow-grey')
|
||||||
|
.attr('viewBox', '0 -5 10 10')
|
||||||
|
.attr('refX', 18)
|
||||||
|
.attr('refY', 0)
|
||||||
|
.attr('markerWidth', 6)
|
||||||
|
.attr('markerHeight', 6)
|
||||||
|
.attr('orient', 'auto')
|
||||||
|
.append('path')
|
||||||
|
.attr('d', 'M0,-4L10,0L0,4')
|
||||||
|
.attr('fill', 'rgba(150, 150, 150, 0.6)');
|
||||||
|
|
||||||
|
// Arrow marker for connected (yellow)
|
||||||
|
defs.append('marker')
|
||||||
|
.attr('id', 'arrow-connected')
|
||||||
|
.attr('viewBox', '0 -5 10 10')
|
||||||
|
.attr('refX', 18)
|
||||||
|
.attr('refY', 0)
|
||||||
|
.attr('markerWidth', 6)
|
||||||
|
.attr('markerHeight', 6)
|
||||||
|
.attr('orient', 'auto')
|
||||||
|
.append('path')
|
||||||
|
.attr('d', 'M0,-4L10,0L0,4')
|
||||||
|
.attr('fill', 'rgba(234, 179, 8, 0.8)');
|
||||||
|
|
||||||
|
// Arrow marker for trusted (green)
|
||||||
|
defs.append('marker')
|
||||||
|
.attr('id', 'arrow-trusted')
|
||||||
|
.attr('viewBox', '0 -5 10 10')
|
||||||
|
.attr('refX', 18)
|
||||||
|
.attr('refY', 0)
|
||||||
|
.attr('markerWidth', 6)
|
||||||
|
.attr('markerHeight', 6)
|
||||||
|
.attr('orient', 'auto')
|
||||||
|
.append('path')
|
||||||
|
.attr('d', 'M0,-4L10,0L0,4')
|
||||||
|
.attr('fill', 'rgba(34, 197, 94, 0.8)');
|
||||||
|
|
||||||
// Helper to get edge color based on trust level
|
// Helper to get edge color based on trust level
|
||||||
const getEdgeColor = (d: SimulationLink) => {
|
const getEdgeColor = (d: SimulationLink) => {
|
||||||
const edge = edges.find(e => e.id === d.id);
|
const edge = edges.find(e => e.id === d.id);
|
||||||
if (!edge) return 'rgba(0, 0, 0, 0.15)';
|
if (!edge) return 'rgba(150, 150, 150, 0.4)';
|
||||||
|
|
||||||
// Use effective trust level for mutual connections, otherwise the edge's trust level
|
// Use effective trust level for mutual connections, otherwise the edge's trust level
|
||||||
const level = edge.effectiveTrustLevel || edge.trustLevel;
|
const level = edge.effectiveTrustLevel || edge.trustLevel;
|
||||||
if (level === 'trusted') {
|
if (level === 'trusted') {
|
||||||
return 'rgba(34, 197, 94, 0.6)'; // green
|
return 'rgba(34, 197, 94, 0.7)'; // green
|
||||||
} else if (level === 'connected') {
|
} else if (level === 'connected') {
|
||||||
return 'rgba(234, 179, 8, 0.6)'; // yellow
|
return 'rgba(234, 179, 8, 0.7)'; // yellow
|
||||||
}
|
}
|
||||||
return 'rgba(0, 0, 0, 0.15)';
|
return 'rgba(150, 150, 150, 0.4)';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create edges
|
// Helper to get arrow marker based on trust level
|
||||||
|
const getArrowMarker = (d: SimulationLink) => {
|
||||||
|
const edge = edges.find(e => e.id === d.id);
|
||||||
|
if (!edge) return 'url(#arrow-grey)';
|
||||||
|
const level = edge.effectiveTrustLevel || edge.trustLevel;
|
||||||
|
if (level === 'trusted') return 'url(#arrow-trusted)';
|
||||||
|
if (level === 'connected') return 'url(#arrow-connected)';
|
||||||
|
return 'url(#arrow-grey)';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create edges as paths (lines) with arrow markers
|
||||||
const link = g.append('g')
|
const link = g.append('g')
|
||||||
.attr('class', 'links')
|
.attr('class', 'links')
|
||||||
.selectAll('line')
|
.selectAll('line')
|
||||||
|
|
@ -237,6 +291,7 @@ export function NetworkGraphMinimap({
|
||||||
.join('line')
|
.join('line')
|
||||||
.attr('stroke', d => getEdgeColor(d))
|
.attr('stroke', d => getEdgeColor(d))
|
||||||
.attr('stroke-width', d => d.isMutual ? 2.5 : 1.5)
|
.attr('stroke-width', d => d.isMutual ? 2.5 : 1.5)
|
||||||
|
.attr('marker-end', d => getArrowMarker(d))
|
||||||
.style('cursor', 'pointer')
|
.style('cursor', 'pointer')
|
||||||
.on('click', (event, d) => {
|
.on('click', (event, d) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
|
||||||
|
|
@ -1339,11 +1339,23 @@ export function MycelialIntelligenceBar() {
|
||||||
}
|
}
|
||||||
}, [editor, suggestedTools, spawnedToolIds])
|
}, [editor, suggestedTools, spawnedToolIds])
|
||||||
|
|
||||||
|
// Responsive layout - detect window width
|
||||||
|
const [windowWidth, setWindowWidth] = useState(typeof window !== 'undefined' ? window.innerWidth : 1024)
|
||||||
|
const isMobile = windowWidth < 640
|
||||||
|
const isNarrow = windowWidth < 768
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => setWindowWidth(window.innerWidth)
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
return () => window.removeEventListener('resize', handleResize)
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Height: taller when showing suggestion chips (single tool or 2+ selected)
|
// Height: taller when showing suggestion chips (single tool or 2+ selected)
|
||||||
const showSuggestions = selectedToolInfo || (selectionInfo && selectionInfo.count > 1)
|
const showSuggestions = selectedToolInfo || (selectionInfo && selectionInfo.count > 1)
|
||||||
const collapsedHeight = showSuggestions ? 76 : 48
|
const collapsedHeight = showSuggestions ? 76 : 48
|
||||||
const maxExpandedHeight = 400
|
const maxExpandedHeight = isMobile ? 300 : 400
|
||||||
const barWidth = 520 // Consistent width
|
// Responsive width: full width on mobile, percentage on narrow, fixed on desktop
|
||||||
|
const barWidth = isMobile ? 'calc(100% - 20px)' : isNarrow ? 'calc(100% - 120px)' : 520
|
||||||
|
|
||||||
// Calculate dynamic height when expanded based on content
|
// Calculate dynamic height when expanded based on content
|
||||||
// Header: ~45px, Input area: ~56px, padding: ~24px = ~125px fixed
|
// Header: ~45px, Input area: ~56px, padding: ~24px = ~125px fixed
|
||||||
|
|
@ -1360,17 +1372,20 @@ export function MycelialIntelligenceBar() {
|
||||||
className="mycelial-intelligence-bar"
|
className="mycelial-intelligence-bar"
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
top: '10px',
|
// On mobile: bottom of screen, on desktop: top center
|
||||||
|
top: isMobile ? 'auto' : '10px',
|
||||||
|
bottom: isMobile ? '70px' : 'auto', // Above bottom toolbar on mobile
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translateX(-50%)',
|
transform: 'translateX(-50%)',
|
||||||
width: barWidth,
|
width: barWidth,
|
||||||
|
maxWidth: isMobile ? 'none' : '520px',
|
||||||
height: isExpanded ? 'auto' : collapsedHeight,
|
height: isExpanded ? 'auto' : collapsedHeight,
|
||||||
minHeight: isExpanded ? minExpandedHeight : collapsedHeight,
|
minHeight: isExpanded ? minExpandedHeight : collapsedHeight,
|
||||||
maxHeight: isExpanded ? maxExpandedHeight : collapsedHeight,
|
maxHeight: isExpanded ? maxExpandedHeight : collapsedHeight,
|
||||||
zIndex: isModalOpen ? 1 : 99999, // Lower z-index when modals are open
|
zIndex: isModalOpen ? 1 : 99999, // Lower z-index when modals are open
|
||||||
pointerEvents: isModalOpen ? 'none' : 'auto', // Disable interactions when modal is open
|
pointerEvents: isModalOpen ? 'none' : 'auto', // Disable interactions when modal is open
|
||||||
opacity: isModalOpen ? 0.3 : 1, // Fade when modal is open
|
opacity: isModalOpen ? 0.3 : 1, // Fade when modal is open
|
||||||
transition: 'opacity 0.2s ease, z-index 0s',
|
transition: 'opacity 0.2s ease, z-index 0s, top 0.3s ease, bottom 0.3s ease, width 0.3s ease',
|
||||||
}}
|
}}
|
||||||
onPointerEnter={() => setIsHovering(true)}
|
onPointerEnter={() => setIsHovering(true)}
|
||||||
onPointerLeave={() => setIsHovering(false)}
|
onPointerLeave={() => setIsHovering(false)}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue