Fix Help page scroll, widen Connected Integrations grid, color-code System Status ring

- Help page now scrolls (it sat under a clipped section with no overflow handling).
- Connected Integrations on Glance shows 5 per row in a scrollable area with the
  transparent ghost scrollbar, instead of growing the card unbounded.
- System Status KPI ring is now a thicker, vertically centered, multi-segment
  donut broken down by integration type, each type colored consistently from a
  shared first-come-first-served palette (src/lib/integrationColors.ts) so e.g.
  whichever type connects first always gets the same color everywhere it's used.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hu9pZvJY4BgmcQeAw2ugk
This commit is contained in:
Claude 2026-06-21 09:21:56 +00:00
parent a964591431
commit 4674f4e209
No known key found for this signature in database
5 changed files with 97 additions and 5 deletions

View file

@ -70,7 +70,7 @@ export default function BottomRow() {
) : integrations.length === 0 ? (
<p style={{ fontSize: '12px', color: '#7A7D85' }}>No integrations added yet add one in Settings.</p>
) : (
<div className="grid grid-cols-3 gap-3">
<div className="scrollbar-ghost grid grid-cols-5 gap-2" style={{ maxHeight: '168px', overflowY: 'auto', paddingRight: '4px' }}>
{integrations.map((i) => (
<div key={i.id} className="flex items-center gap-2.5" style={{ padding: '8px 10px', borderRadius: '8px', backgroundColor: 'rgba(255,255,255,0.02)' }}>
<span style={{ width: '7px', height: '7px', borderRadius: '50%', backgroundColor: statusColor[i.status] ?? '#7A7D85', flexShrink: 0 }} />

View file

@ -49,3 +49,53 @@ export default function ProgressRing({ percentage, size = 56, strokeWidth = 4 }:
</div>
)
}
interface RingSegment {
color: string
value: number
}
interface MultiSegmentRingProps {
segments: RingSegment[]
total: number
size?: number
strokeWidth?: number
centerLabel?: string
}
export function MultiSegmentRing({ segments, total, size = 64, strokeWidth = 10, centerLabel }: MultiSegmentRingProps) {
const radius = (size - strokeWidth) / 2
const circumference = 2 * Math.PI * radius
let cumulative = 0
const drawn = segments.filter((s) => s.value > 0)
return (
<div className="relative flex items-center justify-center" style={{ width: size, height: size }}>
<svg width={size} height={size} className="-rotate-90">
<circle cx={size / 2} cy={size / 2} r={radius} fill="none" stroke="#1E2025" strokeWidth={strokeWidth} />
{total > 0 &&
drawn.map((seg, i) => {
const segLen = (seg.value / total) * circumference
const offset = circumference - cumulative
cumulative += segLen
return (
<circle
key={i}
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke={seg.color}
strokeWidth={strokeWidth}
strokeDasharray={`${segLen} ${circumference - segLen}`}
strokeDashoffset={offset}
className="transition-all duration-1000 ease-out"
/>
)
})}
</svg>
{centerLabel && <span className="absolute text-sm font-bold text-text-primary">{centerLabel}</span>}
</div>
)
}

View file

@ -1,7 +1,8 @@
import { useEffect, useState } from 'react'
import { Server, Plug, BookMarked } from 'lucide-react'
import ProgressRing from './ProgressRing'
import { MultiSegmentRing } from './ProgressRing'
import { api, type Integration, type Resource, type Bookmark } from '../lib/api'
import { getIntegrationTypeColors } from '../lib/integrationColors'
const cardStyle: React.CSSProperties = {
backgroundColor: 'rgba(10, 10, 12, 0.55)',
@ -47,17 +48,34 @@ export default function StatusCards() {
const systemLabel = errored > 0 ? 'Issues Detected' : total === 0 ? 'Not Configured' : 'All Systems Operational'
const systemPercent = total === 0 ? 0 : Math.round((connected / total) * 100)
const typeColors = getIntegrationTypeColors(integrations ?? [])
const typeCounts = new Map<string, number>()
for (const i of integrations ?? []) {
typeCounts.set(i.type, (typeCounts.get(i.type) ?? 0) + 1)
}
const typeSegments = [...typeCounts.entries()].map(([type, value]) => ({ type, value, color: typeColors[type] }))
return (
<div className="grid w-full grid-cols-4 gap-5">
{/* System Status */}
<div style={cardStyle} className="hover:!border-gold/20">
<div className="flex items-start justify-between">
<div className="flex h-full items-center justify-between">
<div>
<h3 style={labelStyle}>System Status</h3>
<p style={{ fontSize: '16px', fontWeight: 700, color: errored > 0 ? '#E74C3C' : '#2ECC71', lineHeight: 1.3 }}>{systemLabel}</p>
<p style={{ fontSize: '10px', color: '#7A7D85', marginTop: '4px' }}>{connected} of {total} integrations connected</p>
{typeSegments.length > 0 && (
<div className="flex items-center gap-2 flex-wrap" style={{ marginTop: '8px' }}>
{typeSegments.map((s) => (
<span key={s.type} className="flex items-center gap-1">
<span style={{ width: '6px', height: '6px', borderRadius: '50%', backgroundColor: s.color }} />
<span style={{ fontSize: '9px', color: '#7A7D85', textTransform: 'capitalize' }}>{s.type} ({s.value})</span>
</span>
))}
</div>
<ProgressRing percentage={systemPercent} size={44} strokeWidth={3} />
)}
</div>
<MultiSegmentRing segments={typeSegments} total={total} size={72} strokeWidth={11} centerLabel={`${systemPercent}%`} />
</div>
</div>

View file

@ -0,0 +1,24 @@
import type { Integration } from './api'
// Color-blind-friendly palette (Blue/Orange/Green/Brown/Purple/Red/Teal/Yellow),
// assigned to integration *types* in first-come-first-served order (by integration
// id, i.e. whichever type was connected to ArchNest first). Once a type has a
// color it keeps it everywhere — System Status breakdown, Infrastructure, etc.
const PALETTE = ['#4A90E2', '#E67E22', '#2ECC71', '#8B5E3C', '#9B59B6', '#E74C3C', '#1ABC9C', '#F1C40F']
export function getIntegrationTypeColors(integrations: Integration[]): Record<string, string> {
const sorted = [...integrations].sort((a, b) => a.id - b.id)
const map: Record<string, string> = {}
let next = 0
for (const integ of sorted) {
if (!(integ.type in map)) {
map[integ.type] = PALETTE[next % PALETTE.length]
next++
}
}
return map
}
export function getIntegrationColor(integrations: Integration[], type: string): string {
return getIntegrationTypeColors(integrations)[type] ?? '#7A7D85'
}

View file

@ -160,7 +160,7 @@ const quickStartSteps = [
export default function Help() {
return (
<div className="p-8" style={{ maxWidth: '1100px' }}>
<div className="scrollbar-ghost h-full overflow-y-auto p-8" style={{ maxWidth: '1100px' }}>
<div style={{ marginBottom: '24px' }}>
<h1 style={{ fontSize: '22px', color: '#E8E6E0', fontWeight: 700, marginBottom: '6px' }}>How ArchNest works</h1>
<p style={{ fontSize: '13px', color: '#7A7D85' }}>