mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 21:54:56 +00:00
Refactor DocumentationView and FeatureIdeaCloud into modular components
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
@@ -8,10 +8,7 @@ import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
BookOpen,
|
||||
MapPin,
|
||||
FileCode,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
Sparkle,
|
||||
Code,
|
||||
Database,
|
||||
Tree,
|
||||
@@ -28,6 +25,17 @@ import {
|
||||
MagnifyingGlass,
|
||||
GitBranch
|
||||
} from '@phosphor-icons/react'
|
||||
import {
|
||||
FeatureItem,
|
||||
AIFeatureCard,
|
||||
RoadmapItem,
|
||||
AgentFileItem,
|
||||
IntegrationPoint,
|
||||
CICDPlatformItem,
|
||||
PipelineStageCard,
|
||||
SassComponentItem,
|
||||
AnimationItem
|
||||
} from './DocumentationView/DocComponents'
|
||||
|
||||
export function DocumentationView() {
|
||||
const [activeTab, setActiveTab] = useState('readme')
|
||||
@@ -1894,182 +1902,3 @@ docker pull ghcr.io/<username>/<repo>:latest`}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CICDPlatformItem({ name, file, description, features }: {
|
||||
name: string
|
||||
file: string
|
||||
description: string
|
||||
features: string[]
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-3 border-l-2 border-accent pl-4">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<GitBranch size={18} className="text-accent" />
|
||||
<h3 className="text-base font-semibold">{name}</h3>
|
||||
</div>
|
||||
<code className="text-xs text-muted-foreground font-mono">{file}</code>
|
||||
<p className="text-sm text-foreground/90">{description}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">Key Features:</p>
|
||||
<ul className="space-y-1">
|
||||
{features.map((feature, idx) => (
|
||||
<li key={idx} className="text-sm text-foreground/80 flex items-start gap-2">
|
||||
<CheckCircle size={14} weight="fill" className="text-accent mt-1 flex-shrink-0" />
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function PipelineStageCard({ stage, description, duration }: {
|
||||
stage: string
|
||||
description: string
|
||||
duration: string
|
||||
}) {
|
||||
return (
|
||||
<Card className="bg-primary/5 border-primary/20">
|
||||
<CardContent className="pt-4 pb-4">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1 flex-1">
|
||||
<h4 className="font-semibold text-sm">{stage}</h4>
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
<Badge variant="secondary" className="text-xs whitespace-nowrap">
|
||||
{duration}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function SassComponentItem({ name, classes, description }: { name: string; classes: string[]; description: string }) {
|
||||
return (
|
||||
<div className="space-y-2 p-4 border rounded-lg bg-card">
|
||||
<h4 className="font-semibold">{name}</h4>
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
<div className="space-y-1">
|
||||
{classes.map((cls, idx) => (
|
||||
<code key={idx} className="text-xs font-mono text-accent block">{cls}</code>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function AnimationItem({ name, description }: { name: string; description: string }) {
|
||||
return (
|
||||
<div className="space-y-1 p-3 border rounded-lg bg-card">
|
||||
<code className="text-xs font-mono text-accent">{name}</code>
|
||||
<p className="text-xs text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FeatureItem({ icon, title, description }: { icon: React.ReactNode; title: string; description: string }) {
|
||||
return (
|
||||
<div className="flex gap-3">
|
||||
<div className="text-accent mt-0.5">{icon}</div>
|
||||
<div className="space-y-1">
|
||||
<h4 className="font-semibold text-sm">{title}</h4>
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function AIFeatureCard({ title, description }: { title: string; description: string }) {
|
||||
return (
|
||||
<Card className="bg-primary/5 border-primary/20">
|
||||
<CardContent className="pt-4 pb-4">
|
||||
<div className="flex gap-3">
|
||||
<Sparkle size={20} weight="duotone" className="text-accent flex-shrink-0 mt-0.5" />
|
||||
<div className="space-y-1">
|
||||
<h4 className="font-semibold text-sm">{title}</h4>
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function RoadmapItem({ status, title, description, version }: {
|
||||
status: 'completed' | 'planned'
|
||||
title: string
|
||||
description: string
|
||||
version: string
|
||||
}) {
|
||||
return (
|
||||
<Card className={status === 'completed' ? 'bg-green-500/5 border-green-500/20' : 'bg-muted/50'}>
|
||||
<CardContent className="pt-4 pb-4">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="font-semibold">{title}</h4>
|
||||
<Badge variant={status === 'completed' ? 'default' : 'secondary'} className="text-xs">
|
||||
{version}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function AgentFileItem({ filename, path, description, features }: {
|
||||
filename: string
|
||||
path: string
|
||||
description: string
|
||||
features: string[]
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-3 border-l-2 border-accent pl-4">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileCode size={18} className="text-accent" />
|
||||
<code className="text-sm font-semibold text-accent">{filename}</code>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-mono">{path}</p>
|
||||
<p className="text-sm text-foreground/90">{description}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">Key Features:</p>
|
||||
<ul className="space-y-1">
|
||||
{features.map((feature, idx) => (
|
||||
<li key={idx} className="text-sm text-foreground/80 flex items-start gap-2">
|
||||
<CheckCircle size={14} weight="fill" className="text-accent mt-1 flex-shrink-0" />
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function IntegrationPoint({ component, capabilities }: { component: string; capabilities: string[] }) {
|
||||
return (
|
||||
<div className="space-y-2 border rounded-lg p-4 bg-card">
|
||||
<h4 className="font-semibold text-sm flex items-center gap-2">
|
||||
<Sparkle size={16} weight="duotone" className="text-accent" />
|
||||
{component}
|
||||
</h4>
|
||||
<ul className="space-y-1">
|
||||
{capabilities.map((capability, idx) => (
|
||||
<li key={idx} className="text-sm text-muted-foreground flex items-start gap-2">
|
||||
<span className="text-accent">•</span>
|
||||
<span>{capability}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
182
src/components/DocumentationView/DocComponents.tsx
Normal file
182
src/components/DocumentationView/DocComponents.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { FileCode, CheckCircle, Sparkle } from '@phosphor-icons/react'
|
||||
|
||||
export function FeatureItem({ icon, title, description }: { icon: React.ReactNode; title: string; description: string }) {
|
||||
return (
|
||||
<div className="flex gap-3">
|
||||
<div className="text-accent mt-0.5">{icon}</div>
|
||||
<div className="space-y-1">
|
||||
<h4 className="font-semibold text-sm">{title}</h4>
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function AIFeatureCard({ title, description }: { title: string; description: string }) {
|
||||
return (
|
||||
<Card className="bg-primary/5 border-primary/20">
|
||||
<CardContent className="pt-4 pb-4">
|
||||
<div className="flex gap-3">
|
||||
<Sparkle size={20} weight="duotone" className="text-accent flex-shrink-0 mt-0.5" />
|
||||
<div className="space-y-1">
|
||||
<h4 className="font-semibold text-sm">{title}</h4>
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export function RoadmapItem({ status, title, description, version }: {
|
||||
status: 'completed' | 'planned'
|
||||
title: string
|
||||
description: string
|
||||
version: string
|
||||
}) {
|
||||
return (
|
||||
<Card className={status === 'completed' ? 'bg-green-500/5 border-green-500/20' : 'bg-muted/50'}>
|
||||
<CardContent className="pt-4 pb-4">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="font-semibold">{title}</h4>
|
||||
<Badge variant={status === 'completed' ? 'default' : 'secondary'} className="text-xs">
|
||||
{version}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export function AgentFileItem({ filename, path, description, features }: {
|
||||
filename: string
|
||||
path: string
|
||||
description: string
|
||||
features: string[]
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-3 border-l-2 border-accent pl-4">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileCode size={18} className="text-accent" />
|
||||
<code className="text-sm font-semibold text-accent">{filename}</code>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-mono">{path}</p>
|
||||
<p className="text-sm text-foreground/90">{description}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">Key Features:</p>
|
||||
<ul className="space-y-1">
|
||||
{features.map((feature, idx) => (
|
||||
<li key={idx} className="text-sm text-foreground/80 flex items-start gap-2">
|
||||
<CheckCircle size={14} weight="fill" className="text-accent mt-1 flex-shrink-0" />
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function IntegrationPoint({ component, capabilities }: { component: string; capabilities: string[] }) {
|
||||
return (
|
||||
<div className="space-y-2 border rounded-lg p-4 bg-card">
|
||||
<h4 className="font-semibold text-sm flex items-center gap-2">
|
||||
<Sparkle size={16} weight="duotone" className="text-accent" />
|
||||
{component}
|
||||
</h4>
|
||||
<ul className="space-y-1">
|
||||
{capabilities.map((capability, idx) => (
|
||||
<li key={idx} className="text-sm text-muted-foreground flex items-start gap-2">
|
||||
<span className="text-accent">•</span>
|
||||
<span>{capability}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function CICDPlatformItem({ name, file, description, features }: {
|
||||
name: string
|
||||
file: string
|
||||
description: string
|
||||
features: string[]
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-3 border-l-2 border-accent pl-4">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileCode size={18} className="text-accent" />
|
||||
<h3 className="text-base font-semibold">{name}</h3>
|
||||
</div>
|
||||
<code className="text-xs text-muted-foreground font-mono">{file}</code>
|
||||
<p className="text-sm text-foreground/90">{description}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">Key Features:</p>
|
||||
<ul className="space-y-1">
|
||||
{features.map((feature, idx) => (
|
||||
<li key={idx} className="text-sm text-foreground/80 flex items-start gap-2">
|
||||
<CheckCircle size={14} weight="fill" className="text-accent mt-1 flex-shrink-0" />
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function PipelineStageCard({ stage, description, duration }: {
|
||||
stage: string
|
||||
description: string
|
||||
duration: string
|
||||
}) {
|
||||
return (
|
||||
<Card className="bg-primary/5 border-primary/20">
|
||||
<CardContent className="pt-4 pb-4">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1 flex-1">
|
||||
<h4 className="font-semibold text-sm">{stage}</h4>
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
<Badge variant="secondary" className="text-xs whitespace-nowrap">
|
||||
{duration}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export function SassComponentItem({ name, classes, description }: { name: string; classes: string[]; description: string }) {
|
||||
return (
|
||||
<div className="space-y-2 p-4 border rounded-lg bg-card">
|
||||
<h4 className="font-semibold">{name}</h4>
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
<div className="space-y-1">
|
||||
{classes.map((cls, idx) => (
|
||||
<code key={idx} className="text-xs font-mono text-accent block">{cls}</code>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function AnimationItem({ name, description }: { name: string; description: string }) {
|
||||
return (
|
||||
<div className="space-y-1 p-3 border rounded-lg bg-card">
|
||||
<code className="text-xs font-mono text-accent">{name}</code>
|
||||
<p className="text-xs text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
/// <reference path="../global.d.ts" />
|
||||
|
||||
import { useState, useEffect, useCallback, useRef, ReactElement } from 'react'
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { useKV } from '@/hooks/use-kv'
|
||||
import ReactFlow, {
|
||||
Node,
|
||||
@@ -14,307 +12,20 @@ import ReactFlow, {
|
||||
MarkerType,
|
||||
ConnectionMode,
|
||||
Panel,
|
||||
NodeProps,
|
||||
Handle,
|
||||
Position,
|
||||
reconnectEdge,
|
||||
} from 'reactflow'
|
||||
import 'reactflow/dist/style.css'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Plus, Trash, Sparkle, DotsThree, Package } from '@phosphor-icons/react'
|
||||
import { Plus, Trash, Sparkle, Package } from '@phosphor-icons/react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
interface FeatureIdea {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
category: string
|
||||
priority: 'low' | 'medium' | 'high'
|
||||
status: 'idea' | 'planned' | 'in-progress' | 'completed'
|
||||
createdAt: number
|
||||
parentGroup?: string
|
||||
}
|
||||
|
||||
interface IdeaGroup {
|
||||
id: string
|
||||
label: string
|
||||
color: string
|
||||
createdAt: number
|
||||
}
|
||||
|
||||
interface IdeaEdgeData {
|
||||
label?: string
|
||||
}
|
||||
|
||||
const SEED_IDEAS: FeatureIdea[] = [
|
||||
{
|
||||
id: 'idea-1',
|
||||
title: 'AI Code Assistant',
|
||||
description: 'Integrate an AI assistant that can suggest code improvements and answer questions',
|
||||
category: 'AI/ML',
|
||||
priority: 'high',
|
||||
status: 'completed',
|
||||
createdAt: Date.now() - 10000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-2',
|
||||
title: 'Real-time Collaboration',
|
||||
description: 'Allow multiple developers to work on the same project simultaneously',
|
||||
category: 'Collaboration',
|
||||
priority: 'high',
|
||||
status: 'idea',
|
||||
createdAt: Date.now() - 9000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-3',
|
||||
title: 'Component Marketplace',
|
||||
description: 'A marketplace where users can share and download pre-built components',
|
||||
category: 'Community',
|
||||
priority: 'medium',
|
||||
status: 'idea',
|
||||
createdAt: Date.now() - 8000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-4',
|
||||
title: 'Visual Git Integration',
|
||||
description: 'Git operations through a visual interface with branch visualization',
|
||||
category: 'DevOps',
|
||||
priority: 'high',
|
||||
status: 'planned',
|
||||
createdAt: Date.now() - 7000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-5',
|
||||
title: 'API Mock Server',
|
||||
description: 'Built-in mock server for testing API integrations',
|
||||
category: 'Testing',
|
||||
priority: 'medium',
|
||||
status: 'idea',
|
||||
createdAt: Date.now() - 6000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-6',
|
||||
title: 'Performance Profiler',
|
||||
description: 'Analyze and optimize application performance with visual metrics',
|
||||
category: 'Performance',
|
||||
priority: 'medium',
|
||||
status: 'idea',
|
||||
createdAt: Date.now() - 5000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-7',
|
||||
title: 'Theme Presets',
|
||||
description: 'Pre-designed theme templates for quick project setup',
|
||||
category: 'Design',
|
||||
priority: 'low',
|
||||
status: 'completed',
|
||||
createdAt: Date.now() - 4000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-8',
|
||||
title: 'Database Schema Migrations',
|
||||
description: 'Visual tool for creating and managing database migrations',
|
||||
category: 'Database',
|
||||
priority: 'high',
|
||||
status: 'in-progress',
|
||||
createdAt: Date.now() - 3000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-9',
|
||||
title: 'Mobile App Preview',
|
||||
description: 'Live preview on actual mobile devices or simulators',
|
||||
category: 'Mobile',
|
||||
priority: 'medium',
|
||||
status: 'planned',
|
||||
createdAt: Date.now() - 2000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-10',
|
||||
title: 'Accessibility Checker',
|
||||
description: 'Automated accessibility testing and suggestions',
|
||||
category: 'Accessibility',
|
||||
priority: 'high',
|
||||
status: 'idea',
|
||||
createdAt: Date.now() - 1000000,
|
||||
},
|
||||
]
|
||||
|
||||
const CATEGORIES = ['AI/ML', 'Collaboration', 'Community', 'DevOps', 'Testing', 'Performance', 'Design', 'Database', 'Mobile', 'Accessibility', 'Productivity', 'Security', 'Analytics', 'Other']
|
||||
const PRIORITIES = ['low', 'medium', 'high'] as const
|
||||
const STATUSES = ['idea', 'planned', 'in-progress', 'completed'] as const
|
||||
|
||||
const CONNECTION_STYLE = {
|
||||
stroke: '#a78bfa',
|
||||
strokeWidth: 2.5
|
||||
}
|
||||
|
||||
const STATUS_COLORS = {
|
||||
idea: 'bg-muted text-muted-foreground',
|
||||
planned: 'bg-accent text-accent-foreground',
|
||||
'in-progress': 'bg-primary text-primary-foreground',
|
||||
completed: 'bg-green-600 text-white',
|
||||
}
|
||||
|
||||
const PRIORITY_COLORS = {
|
||||
low: 'border-blue-400/60 bg-blue-50/80 dark:bg-blue-950/40',
|
||||
medium: 'border-amber-400/60 bg-amber-50/80 dark:bg-amber-950/40',
|
||||
high: 'border-red-400/60 bg-red-50/80 dark:bg-red-950/40',
|
||||
}
|
||||
|
||||
const GROUP_COLORS = [
|
||||
{ name: 'Blue', value: '#3b82f6', bg: 'rgba(59, 130, 246, 0.08)', border: 'rgba(59, 130, 246, 0.3)' },
|
||||
{ name: 'Purple', value: '#a855f7', bg: 'rgba(168, 85, 247, 0.08)', border: 'rgba(168, 85, 247, 0.3)' },
|
||||
{ name: 'Green', value: '#10b981', bg: 'rgba(16, 185, 129, 0.08)', border: 'rgba(16, 185, 129, 0.3)' },
|
||||
{ name: 'Red', value: '#ef4444', bg: 'rgba(239, 68, 68, 0.08)', border: 'rgba(239, 68, 68, 0.3)' },
|
||||
{ name: 'Orange', value: '#f97316', bg: 'rgba(249, 115, 22, 0.08)', border: 'rgba(249, 115, 22, 0.3)' },
|
||||
{ name: 'Pink', value: '#ec4899', bg: 'rgba(236, 72, 153, 0.08)', border: 'rgba(236, 72, 153, 0.3)' },
|
||||
{ name: 'Cyan', value: '#06b6d4', bg: 'rgba(6, 182, 212, 0.08)', border: 'rgba(6, 182, 212, 0.3)' },
|
||||
{ name: 'Amber', value: '#f59e0b', bg: 'rgba(245, 158, 11, 0.08)', border: 'rgba(245, 158, 11, 0.3)' },
|
||||
]
|
||||
|
||||
function GroupNode({ data, selected }: NodeProps<IdeaGroup>) {
|
||||
const colorScheme = GROUP_COLORS.find(c => c.value === data.color) || GROUP_COLORS[0]
|
||||
|
||||
return (
|
||||
<div
|
||||
className="rounded-2xl backdrop-blur-sm transition-all"
|
||||
style={{
|
||||
width: 450,
|
||||
height: 350,
|
||||
backgroundColor: colorScheme.bg,
|
||||
border: `3px dashed ${colorScheme.border}`,
|
||||
boxShadow: selected ? `0 0 0 2px ${colorScheme.value}` : 'none',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute -top-3 left-4 px-3 py-1 rounded-full text-xs font-semibold shadow-md"
|
||||
style={{
|
||||
backgroundColor: colorScheme.value,
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
{data.label}
|
||||
</div>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="absolute -top-2 -right-2 h-7 w-7 rounded-full shadow-md bg-background hover:bg-destructive hover:text-destructive-foreground"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
const event = new CustomEvent('editGroup', { detail: data })
|
||||
window.dispatchEvent(event)
|
||||
}}
|
||||
>
|
||||
<DotsThree size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function IdeaNode({ data, selected, id }: NodeProps<FeatureIdea> & { id: string }) {
|
||||
const [connectionCounts, setConnectionCounts] = useState<Record<string, number>>({
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const updateConnectionCounts = (event: CustomEvent) => {
|
||||
const { nodeId, counts } = event.detail
|
||||
if (nodeId === id) {
|
||||
setConnectionCounts(counts)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('updateConnectionCounts' as any, updateConnectionCounts as EventListener)
|
||||
return () => {
|
||||
window.removeEventListener('updateConnectionCounts' as any, updateConnectionCounts as EventListener)
|
||||
}
|
||||
}, [id])
|
||||
|
||||
const generateHandles = (position: Position, type: 'source' | 'target', side: string) => {
|
||||
const count = connectionCounts[side] || 0
|
||||
const totalHandles = Math.max(2, count + 1)
|
||||
const handles: ReactElement[] = []
|
||||
|
||||
for (let i = 0; i < totalHandles; i++) {
|
||||
const handleId = `${side}-${i}`
|
||||
const isVertical = position === Position.Top || position === Position.Bottom
|
||||
const positionStyle = isVertical
|
||||
? { left: `${((i + 1) / (totalHandles + 1)) * 100}%` }
|
||||
: { top: `${((i + 1) / (totalHandles + 1)) * 100}%` }
|
||||
|
||||
handles.push(
|
||||
<Handle
|
||||
key={handleId}
|
||||
type={type}
|
||||
position={position}
|
||||
id={handleId}
|
||||
className="w-3 h-3 !bg-primary border-2 border-background transition-all hover:scale-125"
|
||||
style={{
|
||||
...positionStyle,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return handles
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{generateHandles(Position.Left, 'target', 'left')}
|
||||
{generateHandles(Position.Right, 'source', 'right')}
|
||||
{generateHandles(Position.Top, 'target', 'top')}
|
||||
{generateHandles(Position.Bottom, 'source', 'bottom')}
|
||||
|
||||
<Card className={`p-4 shadow-xl hover:shadow-2xl transition-all border-2 ${PRIORITY_COLORS[data.priority]} w-[240px] ${selected ? 'ring-2 ring-primary' : ''}`}>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<h3 className="font-semibold text-sm line-clamp-2 flex-1">{data.title}</h3>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 shrink-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
const event = new CustomEvent('editIdea', { detail: data })
|
||||
window.dispatchEvent(event)
|
||||
}}
|
||||
>
|
||||
<DotsThree size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground line-clamp-2">
|
||||
{data.description}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{data.category}
|
||||
</Badge>
|
||||
<Badge className={`text-xs ${STATUS_COLORS[data.status]}`}>
|
||||
{data.status}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const nodeTypes = {
|
||||
ideaNode: IdeaNode,
|
||||
groupNode: GroupNode,
|
||||
}
|
||||
import { FeatureIdea, IdeaGroup, IdeaEdgeData } from './FeatureIdeaCloud/types'
|
||||
import { SEED_IDEAS, CATEGORIES, PRIORITIES, STATUSES, CONNECTION_STYLE, GROUP_COLORS } from './FeatureIdeaCloud/constants'
|
||||
import { nodeTypes } from './FeatureIdeaCloud/nodes'
|
||||
|
||||
export function FeatureIdeaCloud() {
|
||||
const [ideas, setIdeas] = useKV<FeatureIdea[]>('feature-ideas', SEED_IDEAS)
|
||||
|
||||
@@ -1,3 +1,103 @@
|
||||
import { FeatureIdea } from './types'
|
||||
|
||||
export const SEED_IDEAS: FeatureIdea[] = [
|
||||
{
|
||||
id: 'idea-1',
|
||||
title: 'AI Code Assistant',
|
||||
description: 'Integrate an AI assistant that can suggest code improvements and answer questions',
|
||||
category: 'AI/ML',
|
||||
priority: 'high',
|
||||
status: 'completed',
|
||||
createdAt: Date.now() - 10000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-2',
|
||||
title: 'Real-time Collaboration',
|
||||
description: 'Allow multiple developers to work on the same project simultaneously',
|
||||
category: 'Collaboration',
|
||||
priority: 'high',
|
||||
status: 'idea',
|
||||
createdAt: Date.now() - 9000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-3',
|
||||
title: 'Component Marketplace',
|
||||
description: 'A marketplace where users can share and download pre-built components',
|
||||
category: 'Community',
|
||||
priority: 'medium',
|
||||
status: 'idea',
|
||||
createdAt: Date.now() - 8000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-4',
|
||||
title: 'Visual Git Integration',
|
||||
description: 'Git operations through a visual interface with branch visualization',
|
||||
category: 'DevOps',
|
||||
priority: 'high',
|
||||
status: 'planned',
|
||||
createdAt: Date.now() - 7000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-5',
|
||||
title: 'API Mock Server',
|
||||
description: 'Built-in mock server for testing API integrations',
|
||||
category: 'Testing',
|
||||
priority: 'medium',
|
||||
status: 'idea',
|
||||
createdAt: Date.now() - 6000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-6',
|
||||
title: 'Performance Profiler',
|
||||
description: 'Analyze and optimize application performance with visual metrics',
|
||||
category: 'Performance',
|
||||
priority: 'medium',
|
||||
status: 'idea',
|
||||
createdAt: Date.now() - 5000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-7',
|
||||
title: 'Theme Presets',
|
||||
description: 'Pre-designed theme templates for quick project setup',
|
||||
category: 'Design',
|
||||
priority: 'low',
|
||||
status: 'completed',
|
||||
createdAt: Date.now() - 4000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-8',
|
||||
title: 'Database Schema Migrations',
|
||||
description: 'Visual tool for creating and managing database migrations',
|
||||
category: 'Database',
|
||||
priority: 'high',
|
||||
status: 'in-progress',
|
||||
createdAt: Date.now() - 3000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-9',
|
||||
title: 'Mobile App Preview',
|
||||
description: 'Live preview on actual mobile devices or simulators',
|
||||
category: 'Mobile',
|
||||
priority: 'medium',
|
||||
status: 'planned',
|
||||
createdAt: Date.now() - 2000000,
|
||||
},
|
||||
{
|
||||
id: 'idea-10',
|
||||
title: 'Accessibility Checker',
|
||||
description: 'Automated accessibility testing and suggestions',
|
||||
category: 'Accessibility',
|
||||
priority: 'high',
|
||||
status: 'idea',
|
||||
createdAt: Date.now() - 1000000,
|
||||
},
|
||||
]
|
||||
|
||||
export const CONNECTION_STYLE = {
|
||||
stroke: '#a78bfa',
|
||||
strokeWidth: 2.5
|
||||
}
|
||||
|
||||
export const CATEGORIES = [
|
||||
'AI/ML',
|
||||
'Collaboration',
|
||||
|
||||
145
src/components/FeatureIdeaCloud/nodes.tsx
Normal file
145
src/components/FeatureIdeaCloud/nodes.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import { useState, useEffect, ReactElement } from 'react'
|
||||
import { NodeProps, Handle, Position } from 'reactflow'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { DotsThree } from '@phosphor-icons/react'
|
||||
import { FeatureIdea, IdeaGroup } from './types'
|
||||
import { PRIORITY_COLORS, STATUS_COLORS, GROUP_COLORS } from './constants'
|
||||
|
||||
export function GroupNode({ data, selected }: NodeProps<IdeaGroup>) {
|
||||
const colorScheme = GROUP_COLORS.find(c => c.value === data.color) || GROUP_COLORS[0]
|
||||
|
||||
return (
|
||||
<div
|
||||
className="rounded-2xl backdrop-blur-sm transition-all"
|
||||
style={{
|
||||
width: 450,
|
||||
height: 350,
|
||||
backgroundColor: colorScheme.bg,
|
||||
border: `3px dashed ${colorScheme.border}`,
|
||||
boxShadow: selected ? `0 0 0 2px ${colorScheme.value}` : 'none',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute -top-3 left-4 px-3 py-1 rounded-full text-xs font-semibold shadow-md"
|
||||
style={{
|
||||
backgroundColor: colorScheme.value,
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
{data.label}
|
||||
</div>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="absolute -top-2 -right-2 h-7 w-7 rounded-full shadow-md bg-background hover:bg-destructive hover:text-destructive-foreground"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
const event = new CustomEvent('editGroup', { detail: data })
|
||||
window.dispatchEvent(event)
|
||||
}}
|
||||
>
|
||||
<DotsThree size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function IdeaNode({ data, selected, id }: NodeProps<FeatureIdea> & { id: string }) {
|
||||
const [connectionCounts, setConnectionCounts] = useState<Record<string, number>>({
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const updateConnectionCounts = (event: CustomEvent) => {
|
||||
const { nodeId, counts } = event.detail
|
||||
if (nodeId === id) {
|
||||
setConnectionCounts(counts)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('updateConnectionCounts' as any, updateConnectionCounts as EventListener)
|
||||
return () => {
|
||||
window.removeEventListener('updateConnectionCounts' as any, updateConnectionCounts as EventListener)
|
||||
}
|
||||
}, [id])
|
||||
|
||||
const generateHandles = (position: Position, type: 'source' | 'target', side: string) => {
|
||||
const count = connectionCounts[side] || 0
|
||||
const totalHandles = Math.max(2, count + 1)
|
||||
const handles: ReactElement[] = []
|
||||
|
||||
for (let i = 0; i < totalHandles; i++) {
|
||||
const handleId = `${side}-${i}`
|
||||
const isVertical = position === Position.Top || position === Position.Bottom
|
||||
const positionStyle = isVertical
|
||||
? { left: `${((i + 1) / (totalHandles + 1)) * 100}%` }
|
||||
: { top: `${((i + 1) / (totalHandles + 1)) * 100}%` }
|
||||
|
||||
handles.push(
|
||||
<Handle
|
||||
key={handleId}
|
||||
type={type}
|
||||
position={position}
|
||||
id={handleId}
|
||||
className="w-3 h-3 !bg-primary border-2 border-background transition-all hover:scale-125"
|
||||
style={{
|
||||
...positionStyle,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return handles
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{generateHandles(Position.Left, 'target', 'left')}
|
||||
{generateHandles(Position.Right, 'source', 'right')}
|
||||
{generateHandles(Position.Top, 'target', 'top')}
|
||||
{generateHandles(Position.Bottom, 'source', 'bottom')}
|
||||
|
||||
<Card className={`p-4 shadow-xl hover:shadow-2xl transition-all border-2 ${PRIORITY_COLORS[data.priority]} w-[240px] ${selected ? 'ring-2 ring-primary' : ''}`}>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<h3 className="font-semibold text-sm line-clamp-2 flex-1">{data.title}</h3>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 shrink-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
const event = new CustomEvent('editIdea', { detail: data })
|
||||
window.dispatchEvent(event)
|
||||
}}
|
||||
>
|
||||
<DotsThree size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground line-clamp-2">
|
||||
{data.description}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{data.category}
|
||||
</Badge>
|
||||
<Badge className={`text-xs ${STATUS_COLORS[data.status]}`}>
|
||||
{data.status}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const nodeTypes = {
|
||||
ideaNode: IdeaNode,
|
||||
groupNode: GroupNode,
|
||||
}
|
||||
21
src/components/FeatureIdeaCloud/types.ts
Normal file
21
src/components/FeatureIdeaCloud/types.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export interface FeatureIdea {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
category: string
|
||||
priority: 'low' | 'medium' | 'high'
|
||||
status: 'idea' | 'planned' | 'in-progress' | 'completed'
|
||||
createdAt: number
|
||||
parentGroup?: string
|
||||
}
|
||||
|
||||
export interface IdeaGroup {
|
||||
id: string
|
||||
label: string
|
||||
color: string
|
||||
createdAt: number
|
||||
}
|
||||
|
||||
export interface IdeaEdgeData {
|
||||
label?: string
|
||||
}
|
||||
Reference in New Issue
Block a user