forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfeature-catalog.tsx
More file actions
153 lines (140 loc) · 4.25 KB
/
Copy pathfeature-catalog.tsx
File metadata and controls
153 lines (140 loc) · 4.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"use client";
import Link from "next/link";
import { useMemo } from "react";
import {
type Feature,
type FeatureCategory,
type Integration,
} from "@/lib/registry";
interface FeatureCatalogProps {
features: Feature[];
categories: FeatureCategory[];
integrations: Integration[];
}
interface FeatureWithIntegrations {
feature: Feature;
integrations: Integration[];
}
export function FeatureCatalog({
features,
categories,
integrations,
}: FeatureCatalogProps) {
const deployed = useMemo(
() => integrations.filter((i) => i.deployed),
[integrations],
);
// Group features by category, attaching which deployed integrations support each
const groupedFeatures = useMemo(() => {
const groups: {
category: FeatureCategory;
items: FeatureWithIntegrations[];
}[] = [];
for (const category of categories) {
const categoryFeatures = features.filter(
(f) => f.category === category.id,
);
const items: FeatureWithIntegrations[] = [];
for (const feature of categoryFeatures) {
const supporting = deployed.filter((i) =>
i.features.includes(feature.id),
);
items.push({ feature, integrations: supporting });
}
// Only show categories that have at least one feature with at least one integration
if (items.some((item) => item.integrations.length > 0)) {
groups.push({ category, items });
}
}
return groups;
}, [features, categories, deployed]);
if (groupedFeatures.length === 0) {
return (
<div className="text-center py-16 text-[var(--text-muted)] text-sm">
No features with live integrations yet.
</div>
);
}
return (
<div className="flex flex-col gap-10 max-w-5xl mx-auto">
{groupedFeatures.map(({ category, items }) => (
<section key={category.id}>
<h3 className="text-[13px] font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-4">
{category.name}
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
{items.map(({ feature, integrations: supporting }) => (
<FeatureCard
key={feature.id}
feature={feature}
integrations={supporting}
/>
))}
</div>
</section>
))}
</div>
);
}
function FeatureCard({
feature,
integrations,
}: {
feature: Feature;
integrations: Integration[];
}) {
const hasIntegrations = integrations.length > 0;
return (
<div
className={`rounded-xl border p-4 transition-all ${
hasIntegrations
? "border-[var(--border)] bg-[var(--bg-surface)]"
: "border-transparent bg-[var(--bg-elevated)] opacity-50"
}`}
>
<div className="flex items-start justify-between gap-2 mb-2">
<h4 className="text-sm font-semibold text-[var(--text)]">
{feature.name}
</h4>
{hasIntegrations && (
<Link
href={`/integrations?feature=${feature.id}`}
className="shrink-0 text-[10px] font-medium text-[var(--accent)] hover:underline"
>
View all
</Link>
)}
</div>
<p className="text-[11px] text-[var(--text-secondary)] leading-relaxed mb-3">
{feature.description}
</p>
{hasIntegrations ? (
<div className="flex flex-wrap gap-1.5">
{integrations.map((i) => (
<Link
key={i.slug}
href={`/integrations/${i.slug}`}
className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-medium bg-[var(--accent-light)] text-[var(--accent)] hover:bg-[var(--accent)] hover:text-white transition-colors"
>
{i.logo && (
<img
src={i.logo}
alt=""
className="w-3 h-3 rounded-sm"
onError={(e) => {
(e.target as HTMLImageElement).style.display = "none";
}}
/>
)}
{i.name}
</Link>
))}
</div>
) : (
<span className="text-[10px] text-[var(--text-faint)] italic">
Coming soon
</span>
)}
</div>
);
}