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:
parent
a964591431
commit
4674f4e209
5 changed files with 97 additions and 5 deletions
|
|
@ -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 }} />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
24
src/lib/integrationColors.ts
Normal file
24
src/lib/integrationColors.ts
Normal 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'
|
||||
}
|
||||
|
|
@ -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' }}>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue