@shuffleio/shuffle-mcps 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -0
- package/dist/index.cjs +519 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +163 -0
- package/dist/index.d.ts +163 -0
- package/dist/index.js +483 -0
- package/dist/index.js.map +1 -0
- package/dist/singul-GZKBHJNI.css +488 -0
- package/docs/agent-icon.png +0 -0
- package/docs/app-detail-preview.png +0 -0
- package/docs/search-preview.png +0 -0
- package/package.json +58 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
// SingulJS.tsx
|
|
2
|
+
import React, { useState, useEffect, useRef, useCallback, useMemo, useImperativeHandle } from "react";
|
|
3
|
+
import { algoliasearch } from "algoliasearch";
|
|
4
|
+
import "./singul-GZKBHJNI.css";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
var DEFAULT_ALGOLIA_APP_ID = "JNSS5CFDZZ";
|
|
7
|
+
var DEFAULT_ALGOLIA_API_KEY = "33e4e3564f4f060e96e0531957bed552";
|
|
8
|
+
var DEFAULT_ALGOLIA_INDEX = "appsearch";
|
|
9
|
+
var SingulJS = React.forwardRef(({
|
|
10
|
+
authToken,
|
|
11
|
+
placeholder = "Search apps...",
|
|
12
|
+
layout = "list",
|
|
13
|
+
gridColumns = 3,
|
|
14
|
+
showDescription = false,
|
|
15
|
+
showCategories = false,
|
|
16
|
+
showCheckbox = false,
|
|
17
|
+
multiSelect = false,
|
|
18
|
+
selectedApps = [],
|
|
19
|
+
preventDefault = false,
|
|
20
|
+
inline = false,
|
|
21
|
+
initialQuery = "",
|
|
22
|
+
initialFilterQuery,
|
|
23
|
+
hitsPerPage = 15,
|
|
24
|
+
apiKey,
|
|
25
|
+
apiBaseUrl = "https://shuffler.io",
|
|
26
|
+
authPath = "/api/v1/apps/authentication",
|
|
27
|
+
appAuthPath = "/appauth",
|
|
28
|
+
privateAppsPath = "/api/v1/apps",
|
|
29
|
+
disablePrivateApps = false,
|
|
30
|
+
showSourceFilter = true,
|
|
31
|
+
singulBaseUrl = "https://singul.io",
|
|
32
|
+
hideAuthStatus = false,
|
|
33
|
+
authenticatedApps: externalAuthenticatedApps,
|
|
34
|
+
pinnedApps,
|
|
35
|
+
algoliaAppId = DEFAULT_ALGOLIA_APP_ID,
|
|
36
|
+
algoliaApiKey = DEFAULT_ALGOLIA_API_KEY,
|
|
37
|
+
algoliaIndexName = DEFAULT_ALGOLIA_INDEX,
|
|
38
|
+
customStyles = {},
|
|
39
|
+
className = "",
|
|
40
|
+
renderItem,
|
|
41
|
+
renderEmptyState,
|
|
42
|
+
renderLoadingState,
|
|
43
|
+
onAppSelected,
|
|
44
|
+
onSelectionChange,
|
|
45
|
+
onSearchChange
|
|
46
|
+
}, ref) => {
|
|
47
|
+
const [query, setQuery] = useState(initialQuery);
|
|
48
|
+
const [results, setResults] = useState([]);
|
|
49
|
+
const [privateApps, setPrivateApps] = useState([]);
|
|
50
|
+
const [sourceFilter, setSourceFilter] = useState("all");
|
|
51
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
52
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
53
|
+
const [selectedIndex, setSelectedIndex] = useState(-1);
|
|
54
|
+
const [internalSelectedApps, setInternalSelectedApps] = useState(selectedApps);
|
|
55
|
+
const [authenticatedApps, setAuthenticatedApps] = useState(externalAuthenticatedApps || []);
|
|
56
|
+
const hasInitialized = useRef(false);
|
|
57
|
+
const containerRef = useRef(null);
|
|
58
|
+
const inputRef = useRef(null);
|
|
59
|
+
const debounceTimer = useRef(null);
|
|
60
|
+
const searchClient = useRef(null);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (apiKey) {
|
|
63
|
+
const fetchAuthenticatedApps = async () => {
|
|
64
|
+
try {
|
|
65
|
+
const response = await fetch(`${apiBaseUrl}${authPath}`, {
|
|
66
|
+
headers: {
|
|
67
|
+
"Authorization": `Bearer ${apiKey}`
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
if (response.ok) {
|
|
71
|
+
const result = await response.json();
|
|
72
|
+
const authData = result.data || result;
|
|
73
|
+
if (Array.isArray(authData)) {
|
|
74
|
+
console.log("Loaded authenticated apps:", authData.length, authData.map((a) => a.app?.name));
|
|
75
|
+
setAuthenticatedApps(authData);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error("Failed to fetch authenticated apps:", error);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
fetchAuthenticatedApps();
|
|
83
|
+
}
|
|
84
|
+
}, [apiKey, apiBaseUrl]);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!apiKey || disablePrivateApps) {
|
|
87
|
+
setPrivateApps([]);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const fetchPrivateApps = async () => {
|
|
91
|
+
try {
|
|
92
|
+
const response = await fetch(`${apiBaseUrl}${privateAppsPath}`, {
|
|
93
|
+
headers: { "Authorization": `Bearer ${apiKey}` },
|
|
94
|
+
credentials: "include"
|
|
95
|
+
});
|
|
96
|
+
if (!response.ok) return;
|
|
97
|
+
const data = await response.json();
|
|
98
|
+
const apps = Array.isArray(data) ? data : data?.data || [];
|
|
99
|
+
const normalized = apps.map((a) => ({
|
|
100
|
+
name: a.name || "",
|
|
101
|
+
description: a.description || "",
|
|
102
|
+
objectID: a.id || a.objectID || `private-${a.name}`,
|
|
103
|
+
creator: a.owner || a.creator || "",
|
|
104
|
+
app_version: a.app_version || "1.0.0",
|
|
105
|
+
image_url: a.large_image || a.image_url || "",
|
|
106
|
+
time_edited: a.edited || 0,
|
|
107
|
+
generated: !!a.generated,
|
|
108
|
+
invalid: !!a.invalid,
|
|
109
|
+
priority: a.priority || 0,
|
|
110
|
+
actions: Array.isArray(a.actions) ? a.actions.length : a.actions || 0,
|
|
111
|
+
tags: a.tags || [],
|
|
112
|
+
accessible_by: a.accessible_by || [],
|
|
113
|
+
categories: a.categories || [],
|
|
114
|
+
action_labels: a.action_labels || [],
|
|
115
|
+
triggers: a.triggers || [],
|
|
116
|
+
verified: !!a.verified,
|
|
117
|
+
source: "private"
|
|
118
|
+
}));
|
|
119
|
+
setPrivateApps(normalized);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error("Failed to fetch private apps:", error);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
fetchPrivateApps();
|
|
125
|
+
}, [apiKey, apiBaseUrl, privateAppsPath, disablePrivateApps]);
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (authenticatedApps.length > 0 && results.length > 0) {
|
|
128
|
+
const validatedApps = authenticatedApps.filter((auth) => auth.validation?.valid);
|
|
129
|
+
const appsToAutoSelect = results.filter(
|
|
130
|
+
(app) => validatedApps.some((auth) => auth.app.name.toLowerCase() === app.name.toLowerCase())
|
|
131
|
+
);
|
|
132
|
+
if (appsToAutoSelect.length > 0) {
|
|
133
|
+
const newSelection = [...internalSelectedApps];
|
|
134
|
+
appsToAutoSelect.forEach((app) => {
|
|
135
|
+
if (!newSelection.some((a) => a.objectID === app.objectID)) {
|
|
136
|
+
newSelection.push(app);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
if (newSelection.length !== internalSelectedApps.length) {
|
|
140
|
+
setInternalSelectedApps(newSelection);
|
|
141
|
+
onSelectionChange?.(newSelection);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}, [authenticatedApps, results]);
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (externalAuthenticatedApps) {
|
|
148
|
+
setAuthenticatedApps(externalAuthenticatedApps);
|
|
149
|
+
}
|
|
150
|
+
}, [externalAuthenticatedApps]);
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
searchClient.current = algoliasearch(algoliaAppId, algoliaApiKey);
|
|
153
|
+
if (!hasInitialized.current) {
|
|
154
|
+
hasInitialized.current = true;
|
|
155
|
+
const searchTerm = initialQuery || initialFilterQuery || "";
|
|
156
|
+
setTimeout(() => {
|
|
157
|
+
if (searchClient.current) {
|
|
158
|
+
performSearch(searchTerm);
|
|
159
|
+
onSearchChange?.(initialQuery);
|
|
160
|
+
}
|
|
161
|
+
}, 100);
|
|
162
|
+
}
|
|
163
|
+
}, []);
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
setInternalSelectedApps(selectedApps);
|
|
166
|
+
}, [selectedApps]);
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
const handleClickOutside = (event) => {
|
|
169
|
+
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
170
|
+
setIsOpen(false);
|
|
171
|
+
setSelectedIndex(-1);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
document.addEventListener("click", handleClickOutside);
|
|
175
|
+
return () => document.removeEventListener("click", handleClickOutside);
|
|
176
|
+
}, []);
|
|
177
|
+
const isAppConfigured = useCallback((app) => {
|
|
178
|
+
return authenticatedApps.some(
|
|
179
|
+
(auth) => auth.app.name.toLowerCase() === app.name.toLowerCase() && auth.active === true
|
|
180
|
+
);
|
|
181
|
+
}, [authenticatedApps]);
|
|
182
|
+
const isAppValidated = useCallback((app) => {
|
|
183
|
+
return authenticatedApps.some(
|
|
184
|
+
(auth) => auth.app.name.toLowerCase() === app.name.toLowerCase() && auth.validation?.valid === true
|
|
185
|
+
);
|
|
186
|
+
}, [authenticatedApps]);
|
|
187
|
+
const getAppAuthState = useCallback((app) => {
|
|
188
|
+
return {
|
|
189
|
+
configured: isAppConfigured(app),
|
|
190
|
+
validated: isAppValidated(app)
|
|
191
|
+
};
|
|
192
|
+
}, [isAppConfigured, isAppValidated]);
|
|
193
|
+
const performSearch = useCallback(async (searchQuery) => {
|
|
194
|
+
if (!searchClient.current) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
setIsLoading(true);
|
|
198
|
+
try {
|
|
199
|
+
const searchResult = await searchClient.current.searchSingleIndex({
|
|
200
|
+
indexName: algoliaIndexName,
|
|
201
|
+
searchParams: {
|
|
202
|
+
query: searchQuery || "",
|
|
203
|
+
// Empty string gets top results
|
|
204
|
+
hitsPerPage
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
const hits = searchResult.hits.map((h) => ({ ...h, source: "public" }));
|
|
208
|
+
setResults(hits);
|
|
209
|
+
setIsOpen(hits.length > 0);
|
|
210
|
+
setSelectedIndex(-1);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error("Search failed:", error);
|
|
213
|
+
setResults([]);
|
|
214
|
+
setIsOpen(false);
|
|
215
|
+
} finally {
|
|
216
|
+
setIsLoading(false);
|
|
217
|
+
}
|
|
218
|
+
}, [hitsPerPage]);
|
|
219
|
+
useImperativeHandle(ref, () => ({
|
|
220
|
+
search: (searchQuery) => {
|
|
221
|
+
setQuery(searchQuery);
|
|
222
|
+
onSearchChange?.(searchQuery);
|
|
223
|
+
performSearch(searchQuery);
|
|
224
|
+
},
|
|
225
|
+
clear: () => {
|
|
226
|
+
setQuery("");
|
|
227
|
+
performSearch("");
|
|
228
|
+
},
|
|
229
|
+
getQuery: () => query
|
|
230
|
+
}), [performSearch, onSearchChange, query]);
|
|
231
|
+
const handleInputChange = useCallback((event) => {
|
|
232
|
+
const value = event.target.value;
|
|
233
|
+
setQuery(value);
|
|
234
|
+
setSelectedIndex(-1);
|
|
235
|
+
onSearchChange?.(value);
|
|
236
|
+
if (debounceTimer.current) {
|
|
237
|
+
clearTimeout(debounceTimer.current);
|
|
238
|
+
}
|
|
239
|
+
debounceTimer.current = setTimeout(() => {
|
|
240
|
+
performSearch(value);
|
|
241
|
+
}, 300);
|
|
242
|
+
}, [performSearch, onSearchChange]);
|
|
243
|
+
const selectApp = useCallback((app) => {
|
|
244
|
+
const authUrl = `${apiBaseUrl}${appAuthPath}?app_id=${app.objectID}&auth=${authToken}&source=shuffle`;
|
|
245
|
+
if (multiSelect) {
|
|
246
|
+
const isAlreadySelected = internalSelectedApps.some((a) => a.objectID === app.objectID);
|
|
247
|
+
const newSelection = isAlreadySelected ? internalSelectedApps.filter((a) => a.objectID !== app.objectID) : [...internalSelectedApps, app];
|
|
248
|
+
setInternalSelectedApps(newSelection);
|
|
249
|
+
onSelectionChange?.(newSelection);
|
|
250
|
+
} else {
|
|
251
|
+
onAppSelected?.({ app, authUrl });
|
|
252
|
+
if (!preventDefault) {
|
|
253
|
+
window.open(authUrl, "_blank");
|
|
254
|
+
}
|
|
255
|
+
setIsOpen(false);
|
|
256
|
+
setQuery("");
|
|
257
|
+
setResults([]);
|
|
258
|
+
}
|
|
259
|
+
}, [authToken, apiBaseUrl, appAuthPath, multiSelect, internalSelectedApps, onAppSelected, onSelectionChange, preventDefault]);
|
|
260
|
+
const handleKeyDown = useCallback((event) => {
|
|
261
|
+
if (!isOpen) return;
|
|
262
|
+
switch (event.key) {
|
|
263
|
+
case "ArrowDown":
|
|
264
|
+
event.preventDefault();
|
|
265
|
+
setSelectedIndex((prev) => Math.min(prev + 1, displayResults.length - 1));
|
|
266
|
+
break;
|
|
267
|
+
case "ArrowUp":
|
|
268
|
+
event.preventDefault();
|
|
269
|
+
setSelectedIndex((prev) => Math.max(prev - 1, -1));
|
|
270
|
+
break;
|
|
271
|
+
case "Enter":
|
|
272
|
+
event.preventDefault();
|
|
273
|
+
if (selectedIndex >= 0 && displayResults[selectedIndex]) {
|
|
274
|
+
selectApp(displayResults[selectedIndex]);
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
case "Escape":
|
|
278
|
+
event.preventDefault();
|
|
279
|
+
setIsOpen(false);
|
|
280
|
+
setSelectedIndex(-1);
|
|
281
|
+
inputRef.current?.blur();
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}, [isOpen, results, selectedIndex, selectApp]);
|
|
285
|
+
const isAppSelected = useCallback((app) => {
|
|
286
|
+
return internalSelectedApps.some((a) => a.objectID === app.objectID);
|
|
287
|
+
}, [internalSelectedApps]);
|
|
288
|
+
const filteredPrivateApps = useMemo(() => {
|
|
289
|
+
if (privateApps.length === 0) return [];
|
|
290
|
+
const q = query.trim().toLowerCase();
|
|
291
|
+
if (!q) return privateApps;
|
|
292
|
+
return privateApps.filter(
|
|
293
|
+
(a) => a.name.toLowerCase().includes(q) || (a.description || "").toLowerCase().includes(q) || (a.categories || []).some((c) => c.toLowerCase().includes(q))
|
|
294
|
+
);
|
|
295
|
+
}, [privateApps, query]);
|
|
296
|
+
const displayResults = useMemo(() => {
|
|
297
|
+
const norm = (n) => (n || "").toLowerCase().replace(/[\s_\-]+/g, "");
|
|
298
|
+
let merged;
|
|
299
|
+
if (sourceFilter === "public") {
|
|
300
|
+
merged = results;
|
|
301
|
+
} else if (sourceFilter === "private") {
|
|
302
|
+
merged = filteredPrivateApps;
|
|
303
|
+
} else {
|
|
304
|
+
const privateNames = new Set(filteredPrivateApps.map((a) => norm(a.name)));
|
|
305
|
+
const publicOnly = results.filter((a) => !privateNames.has(norm(a.name)));
|
|
306
|
+
merged = [...filteredPrivateApps, ...publicOnly];
|
|
307
|
+
}
|
|
308
|
+
if (!pinnedApps || pinnedApps.length === 0) return merged;
|
|
309
|
+
const pinnedNames = new Set(pinnedApps.map((a) => norm(a.name)));
|
|
310
|
+
return [...pinnedApps, ...merged.filter((a) => !pinnedNames.has(norm(a.name)))];
|
|
311
|
+
}, [pinnedApps, results, filteredPrivateApps, sourceFilter]);
|
|
312
|
+
const getGridColumnsStyle = useMemo(() => {
|
|
313
|
+
if (layout !== "grid") return {};
|
|
314
|
+
const cols = typeof gridColumns === "number" ? gridColumns : gridColumns.md || 3;
|
|
315
|
+
return { "--singul-grid-columns": cols };
|
|
316
|
+
}, [layout, gridColumns]);
|
|
317
|
+
const renderAppItem = (app, index) => {
|
|
318
|
+
const selected = isAppSelected(app);
|
|
319
|
+
const isHighlighted = index === selectedIndex;
|
|
320
|
+
const authState = getAppAuthState(app);
|
|
321
|
+
if (renderItem) {
|
|
322
|
+
return /* @__PURE__ */ jsx(
|
|
323
|
+
"div",
|
|
324
|
+
{
|
|
325
|
+
onClick: () => selectApp(app),
|
|
326
|
+
onMouseEnter: () => setSelectedIndex(index),
|
|
327
|
+
children: renderItem(app, selected, () => selectApp(app), authState)
|
|
328
|
+
},
|
|
329
|
+
app.objectID
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
return /* @__PURE__ */ jsx(
|
|
333
|
+
"div",
|
|
334
|
+
{
|
|
335
|
+
className: `singul-dropdown-item ${selected ? "singul-selected" : ""} ${authState.validated ? "singul-validated" : ""} ${authState.configured ? "singul-configured" : ""}`,
|
|
336
|
+
style: {
|
|
337
|
+
...customStyles.dropdownItem,
|
|
338
|
+
...selected ? customStyles.selectedItem : {}
|
|
339
|
+
},
|
|
340
|
+
onClick: () => selectApp(app),
|
|
341
|
+
children: /* @__PURE__ */ jsxs("div", { className: "singul-app-info", style: customStyles.appInfo, children: [
|
|
342
|
+
app.image_url && /* @__PURE__ */ jsxs("div", { style: { position: "relative", flexShrink: 0 }, children: [
|
|
343
|
+
/* @__PURE__ */ jsx(
|
|
344
|
+
"img",
|
|
345
|
+
{
|
|
346
|
+
src: app.image_url,
|
|
347
|
+
alt: app.name,
|
|
348
|
+
className: "singul-app-icon",
|
|
349
|
+
style: customStyles.appIcon
|
|
350
|
+
}
|
|
351
|
+
),
|
|
352
|
+
!hideAuthStatus && /* @__PURE__ */ jsx(
|
|
353
|
+
"span",
|
|
354
|
+
{
|
|
355
|
+
className: `singul-status-dot ${authState.validated ? "singul-dot-validated" : authState.configured ? "singul-dot-configured" : selected ? "singul-dot-activated" : "singul-dot-inactive"}`
|
|
356
|
+
}
|
|
357
|
+
)
|
|
358
|
+
] }),
|
|
359
|
+
/* @__PURE__ */ jsxs("div", { className: "singul-app-details", style: customStyles.appDetails, children: [
|
|
360
|
+
/* @__PURE__ */ jsxs("span", { className: "singul-app-name", style: customStyles.appName, children: [
|
|
361
|
+
app.name.replace(/_/g, " "),
|
|
362
|
+
app.source === "private" && /* @__PURE__ */ jsx("span", { className: "singul-private-badge", title: "From your private apps", children: "Private" })
|
|
363
|
+
] }),
|
|
364
|
+
showDescription && app.description && /* @__PURE__ */ jsx("span", { className: "singul-app-description", style: customStyles.appDescription, children: app.description }),
|
|
365
|
+
showCategories && app.categories?.[0] && /* @__PURE__ */ jsx("span", { className: "singul-app-category", style: customStyles.appCategory, children: app.categories[0] })
|
|
366
|
+
] }),
|
|
367
|
+
showCheckbox && /* @__PURE__ */ jsx(
|
|
368
|
+
"div",
|
|
369
|
+
{
|
|
370
|
+
className: `singul-checkbox ${selected ? "singul-checked" : ""}`,
|
|
371
|
+
style: selected ? customStyles.checkboxChecked : customStyles.checkbox,
|
|
372
|
+
children: selected && /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) })
|
|
373
|
+
}
|
|
374
|
+
)
|
|
375
|
+
] })
|
|
376
|
+
},
|
|
377
|
+
app.objectID
|
|
378
|
+
);
|
|
379
|
+
};
|
|
380
|
+
return /* @__PURE__ */ jsx(
|
|
381
|
+
"div",
|
|
382
|
+
{
|
|
383
|
+
ref: containerRef,
|
|
384
|
+
className: `singul-container ${inline ? "singul-inline" : ""} ${className}`,
|
|
385
|
+
style: customStyles.container,
|
|
386
|
+
children: /* @__PURE__ */ jsxs("div", { className: "singul-search-bar-container", children: [
|
|
387
|
+
/* @__PURE__ */ jsxs("div", { className: "singul-search-input-wrapper", style: customStyles.inputWrapper, children: [
|
|
388
|
+
/* @__PURE__ */ jsx(
|
|
389
|
+
"input",
|
|
390
|
+
{
|
|
391
|
+
ref: inputRef,
|
|
392
|
+
type: "text",
|
|
393
|
+
className: "singul-search-input",
|
|
394
|
+
style: customStyles.input,
|
|
395
|
+
placeholder,
|
|
396
|
+
value: query,
|
|
397
|
+
onChange: handleInputChange,
|
|
398
|
+
onFocus: () => {
|
|
399
|
+
if (!inline && query.trim() && results.length > 0) {
|
|
400
|
+
setIsOpen(true);
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
onKeyDown: handleKeyDown,
|
|
404
|
+
autoComplete: "off"
|
|
405
|
+
}
|
|
406
|
+
),
|
|
407
|
+
/* @__PURE__ */ jsx("div", { className: "singul-search-icon", style: customStyles.searchIcon, children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
408
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "8" }),
|
|
409
|
+
/* @__PURE__ */ jsx("path", { d: "m21 21-4.35-4.35" })
|
|
410
|
+
] }) }),
|
|
411
|
+
isLoading && /* @__PURE__ */ jsx("div", { className: "singul-loading-spinner", style: customStyles.loadingSpinner, children: renderLoadingState ? renderLoadingState() : /* @__PURE__ */ jsx("div", { className: "singul-spinner", style: customStyles.spinner }) })
|
|
412
|
+
] }),
|
|
413
|
+
showSourceFilter && privateApps.length > 0 && /* @__PURE__ */ jsx("div", { className: "singul-source-filter", role: "tablist", "aria-label": "Filter by app source", children: [
|
|
414
|
+
{ key: "all", label: "All", count: results.length + filteredPrivateApps.length },
|
|
415
|
+
{ key: "public", label: "Public", count: results.length },
|
|
416
|
+
{ key: "private", label: "Private", count: filteredPrivateApps.length }
|
|
417
|
+
].map((opt) => /* @__PURE__ */ jsxs(
|
|
418
|
+
"button",
|
|
419
|
+
{
|
|
420
|
+
type: "button",
|
|
421
|
+
role: "tab",
|
|
422
|
+
"aria-selected": sourceFilter === opt.key,
|
|
423
|
+
className: `singul-source-pill ${sourceFilter === opt.key ? "singul-source-pill-active" : ""}`,
|
|
424
|
+
onClick: () => setSourceFilter(opt.key),
|
|
425
|
+
children: [
|
|
426
|
+
opt.label,
|
|
427
|
+
/* @__PURE__ */ jsx("span", { className: "singul-source-count", children: opt.count })
|
|
428
|
+
]
|
|
429
|
+
},
|
|
430
|
+
opt.key
|
|
431
|
+
)) }),
|
|
432
|
+
inline && /* @__PURE__ */ jsx(
|
|
433
|
+
"div",
|
|
434
|
+
{
|
|
435
|
+
className: `singul-results-container ${layout === "grid" ? "singul-results-grid" : ""}`,
|
|
436
|
+
style: {
|
|
437
|
+
...customStyles.resultsContainer,
|
|
438
|
+
...getGridColumnsStyle,
|
|
439
|
+
...layout === "grid" ? { display: "grid" } : {}
|
|
440
|
+
},
|
|
441
|
+
children: displayResults.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
442
|
+
displayResults.map((app, index) => renderAppItem(app, index)),
|
|
443
|
+
/* @__PURE__ */ jsx("div", { className: "singul-end-of-results", style: {
|
|
444
|
+
padding: "10px 16px",
|
|
445
|
+
color: "rgba(255, 255, 255, 0.3)",
|
|
446
|
+
textAlign: "center",
|
|
447
|
+
fontSize: "11px",
|
|
448
|
+
gridColumn: "1 / -1"
|
|
449
|
+
}, children: "Can't find what you're looking for? Try a different search term." })
|
|
450
|
+
] }) : query.trim() ? /* @__PURE__ */ jsx("div", { className: "singul-empty-state", style: customStyles.emptyState, children: renderEmptyState ? renderEmptyState() : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
451
|
+
'No integrations match "',
|
|
452
|
+
query,
|
|
453
|
+
'". Try searching for a different app or category.'
|
|
454
|
+
] }) }) : /* @__PURE__ */ jsx("div", { className: "singul-empty-state", style: customStyles.emptyState, children: "Start typing to search integrations..." })
|
|
455
|
+
}
|
|
456
|
+
),
|
|
457
|
+
!inline && isOpen && /* @__PURE__ */ jsx(
|
|
458
|
+
"div",
|
|
459
|
+
{
|
|
460
|
+
className: `singul-dropdown ${layout === "grid" ? "singul-dropdown-grid" : ""}`,
|
|
461
|
+
style: {
|
|
462
|
+
...customStyles.dropdown,
|
|
463
|
+
...getGridColumnsStyle,
|
|
464
|
+
...layout === "grid" ? { display: "grid" } : {}
|
|
465
|
+
},
|
|
466
|
+
children: displayResults.length > 0 ? displayResults.map((app, index) => renderAppItem(app, index)) : /* @__PURE__ */ jsx("div", { className: "singul-empty-state", style: customStyles.emptyState, children: renderEmptyState ? renderEmptyState() : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
467
|
+
'No apps found for "',
|
|
468
|
+
query,
|
|
469
|
+
'"'
|
|
470
|
+
] }) })
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
] })
|
|
474
|
+
}
|
|
475
|
+
);
|
|
476
|
+
});
|
|
477
|
+
SingulJS.displayName = "SingulJS";
|
|
478
|
+
var SingulJS_default = SingulJS;
|
|
479
|
+
export {
|
|
480
|
+
SingulJS,
|
|
481
|
+
SingulJS_default as default
|
|
482
|
+
};
|
|
483
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../SingulJS.tsx"],"sourcesContent":["/**\n * Local Singul Library - React Component\n * This is a local copy for development. Changes here should be merged back to:\n * https://github.com/Shuffle/singul.js\n */\n\nimport React, { useState, useEffect, useRef, useCallback, useMemo, useImperativeHandle } from 'react';\nimport { algoliasearch, SearchClient } from 'algoliasearch';\nimport type { AlgoliaSearchApp, AppSelectedEvent, SingulJSProps, AppAuthentication } from './singul.helpers';\nimport './singul.css';\n\nconst DEFAULT_ALGOLIA_APP_ID = 'JNSS5CFDZZ';\nconst DEFAULT_ALGOLIA_API_KEY = '33e4e3564f4f060e96e0531957bed552';\nconst DEFAULT_ALGOLIA_INDEX = 'appsearch';\n\nexport interface SingulJSHandle {\n search: (query: string) => void;\n clear: () => void;\n}\n\nexport const SingulJS = React.forwardRef<SingulJSHandle, SingulJSProps>(({\n authToken,\n placeholder = 'Search apps...',\n layout = 'list',\n gridColumns = 3,\n showDescription = false,\n showCategories = false,\n showCheckbox = false,\n multiSelect = false,\n selectedApps = [],\n preventDefault = false,\n inline = false,\n initialQuery = '',\n initialFilterQuery,\n hitsPerPage = 15,\n apiKey,\n apiBaseUrl = 'https://shuffler.io',\n authPath = '/api/v1/apps/authentication',\n appAuthPath = '/appauth',\n privateAppsPath = '/api/v1/apps',\n disablePrivateApps = false,\n showSourceFilter = true,\n singulBaseUrl = 'https://singul.io',\n hideAuthStatus = false,\n authenticatedApps: externalAuthenticatedApps,\n pinnedApps,\n algoliaAppId = DEFAULT_ALGOLIA_APP_ID,\n algoliaApiKey = DEFAULT_ALGOLIA_API_KEY,\n algoliaIndexName = DEFAULT_ALGOLIA_INDEX,\n customStyles = {},\n className = '',\n renderItem,\n renderEmptyState,\n renderLoadingState,\n onAppSelected,\n onSelectionChange,\n onSearchChange,\n}, ref) => {\n const [query, setQuery] = useState(initialQuery);\n const [results, setResults] = useState<AlgoliaSearchApp[]>([]);\n const [privateApps, setPrivateApps] = useState<AlgoliaSearchApp[]>([]);\n const [sourceFilter, setSourceFilter] = useState<'all' | 'public' | 'private'>('all');\n const [isLoading, setIsLoading] = useState(false);\n const [isOpen, setIsOpen] = useState(false);\n const [selectedIndex, setSelectedIndex] = useState(-1);\n const [internalSelectedApps, setInternalSelectedApps] = useState<AlgoliaSearchApp[]>(selectedApps);\n const [authenticatedApps, setAuthenticatedApps] = useState<AppAuthentication[]>(externalAuthenticatedApps || []);\n const hasInitialized = useRef(false);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const searchClient = useRef<SearchClient | null>(null);\n\n // Fetch authenticated apps when apiKey is provided\n useEffect(() => {\n if (apiKey) {\n const fetchAuthenticatedApps = async () => {\n try {\n const response = await fetch(`${apiBaseUrl}${authPath}`, {\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n },\n });\n if (response.ok) {\n const result = await response.json();\n // Handle both { success: true, data: [...] } and direct array response\n const authData = result.data || result;\n if (Array.isArray(authData)) {\n console.log('Loaded authenticated apps:', authData.length, authData.map(a => a.app?.name));\n setAuthenticatedApps(authData);\n }\n }\n } catch (error) {\n console.error('Failed to fetch authenticated apps:', error);\n }\n };\n fetchAuthenticatedApps();\n }\n }, [apiKey, apiBaseUrl]);\n\n // Fetch the user's private apps from /api/v1/apps when apiKey is provided.\n // These get merged into search results so users can find their own apps too.\n useEffect(() => {\n if (!apiKey || disablePrivateApps) {\n setPrivateApps([]);\n return;\n }\n const fetchPrivateApps = async () => {\n try {\n const response = await fetch(`${apiBaseUrl}${privateAppsPath}`, {\n headers: { 'Authorization': `Bearer ${apiKey}` },\n credentials: 'include',\n });\n if (!response.ok) return;\n const data = await response.json();\n const apps = Array.isArray(data) ? data : (data?.data || []);\n // Normalize to AlgoliaSearchApp shape, tagged as 'private' source.\n const normalized: AlgoliaSearchApp[] = apps.map((a: any) => ({\n name: a.name || '',\n description: a.description || '',\n objectID: a.id || a.objectID || `private-${a.name}`,\n creator: a.owner || a.creator || '',\n app_version: a.app_version || '1.0.0',\n image_url: a.large_image || a.image_url || '',\n time_edited: a.edited || 0,\n generated: !!a.generated,\n invalid: !!a.invalid,\n priority: a.priority || 0,\n actions: Array.isArray(a.actions) ? a.actions.length : (a.actions || 0),\n tags: a.tags || [],\n accessible_by: a.accessible_by || [],\n categories: a.categories || [],\n action_labels: a.action_labels || [],\n triggers: a.triggers || [],\n verified: !!a.verified,\n source: 'private',\n }));\n setPrivateApps(normalized);\n } catch (error) {\n console.error('Failed to fetch private apps:', error);\n }\n };\n fetchPrivateApps();\n }, [apiKey, apiBaseUrl, privateAppsPath, disablePrivateApps]);\n\n // Auto-select validated apps when they're loaded (validated = tests ran successfully)\n useEffect(() => {\n if (authenticatedApps.length > 0 && results.length > 0) {\n const validatedApps = authenticatedApps.filter(auth => auth.validation?.valid);\n const appsToAutoSelect = results.filter(app => \n validatedApps.some(auth => auth.app.name.toLowerCase() === app.name.toLowerCase())\n );\n \n if (appsToAutoSelect.length > 0) {\n const newSelection = [...internalSelectedApps];\n appsToAutoSelect.forEach(app => {\n if (!newSelection.some(a => a.objectID === app.objectID)) {\n newSelection.push(app);\n }\n });\n if (newSelection.length !== internalSelectedApps.length) {\n setInternalSelectedApps(newSelection);\n onSelectionChange?.(newSelection);\n }\n }\n }\n }, [authenticatedApps, results]);\n\n // Sync external authenticatedApps\n useEffect(() => {\n if (externalAuthenticatedApps) {\n setAuthenticatedApps(externalAuthenticatedApps);\n }\n }, [externalAuthenticatedApps]);\n\n // Initialize Algolia client and run initial search\n useEffect(() => {\n searchClient.current = algoliasearch(algoliaAppId, algoliaApiKey);\n \n // Run initial search after client is ready\n if (!hasInitialized.current) {\n hasInitialized.current = true;\n const searchTerm = initialQuery || initialFilterQuery || '';\n setTimeout(() => {\n if (searchClient.current) {\n performSearch(searchTerm);\n onSearchChange?.(initialQuery); // Only report the visible query\n }\n }, 100);\n }\n }, []);\n\n // Sync external selectedApps\n useEffect(() => {\n setInternalSelectedApps(selectedApps);\n }, [selectedApps]);\n\n // Handle click outside\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\n setIsOpen(false);\n setSelectedIndex(-1);\n }\n };\n\n document.addEventListener('click', handleClickOutside);\n return () => document.removeEventListener('click', handleClickOutside);\n }, []);\n\n // Check if an app is configured (found in authentication API with active: true)\n const isAppConfigured = useCallback((app: AlgoliaSearchApp) => {\n return authenticatedApps.some(\n auth => auth.app.name.toLowerCase() === app.name.toLowerCase() && auth.active === true\n );\n }, [authenticatedApps]);\n\n // Check if an app is validated/tested (validation.valid is true)\n const isAppValidated = useCallback((app: AlgoliaSearchApp) => {\n return authenticatedApps.some(\n auth => auth.app.name.toLowerCase() === app.name.toLowerCase() && auth.validation?.valid === true\n );\n }, [authenticatedApps]);\n\n // Get auth state for an app\n const getAppAuthState = useCallback((app: AlgoliaSearchApp) => {\n return {\n configured: isAppConfigured(app),\n validated: isAppValidated(app),\n };\n }, [isAppConfigured, isAppValidated]);\n\n // Perform search\n const performSearch = useCallback(async (searchQuery: string) => {\n if (!searchClient.current) {\n return;\n }\n\n setIsLoading(true);\n try {\n const searchResult = await searchClient.current.searchSingleIndex({\n indexName: algoliaIndexName,\n searchParams: {\n query: searchQuery || '', // Empty string gets top results\n hitsPerPage,\n },\n });\n\n const hits = (searchResult.hits as AlgoliaSearchApp[]).map(h => ({ ...h, source: 'public' as const }));\n setResults(hits);\n setIsOpen(hits.length > 0);\n setSelectedIndex(-1);\n } catch (error) {\n console.error('Search failed:', error);\n setResults([]);\n setIsOpen(false);\n } finally {\n setIsLoading(false);\n }\n }, [hitsPerPage]);\n\n // Expose imperative methods via ref\n useImperativeHandle(ref, () => ({\n search: (searchQuery: string) => {\n setQuery(searchQuery);\n onSearchChange?.(searchQuery);\n performSearch(searchQuery);\n },\n clear: () => {\n setQuery('');\n performSearch(''); // Show top results when cleared\n },\n getQuery: () => query,\n }), [performSearch, onSearchChange, query]);\n\n // Handle input change\n const handleInputChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n const value = event.target.value;\n setQuery(value);\n setSelectedIndex(-1);\n onSearchChange?.(value);\n\n if (debounceTimer.current) {\n clearTimeout(debounceTimer.current);\n }\n\n debounceTimer.current = setTimeout(() => {\n performSearch(value);\n }, 300);\n }, [performSearch, onSearchChange]);\n\n // Handle app selection\n const selectApp = useCallback((app: AlgoliaSearchApp) => {\n const authUrl = `${apiBaseUrl}${appAuthPath}?app_id=${app.objectID}&auth=${authToken}&source=shuffle`;\n \n if (multiSelect) {\n const isAlreadySelected = internalSelectedApps.some((a) => a.objectID === app.objectID);\n const newSelection = isAlreadySelected\n ? internalSelectedApps.filter((a) => a.objectID !== app.objectID)\n : [...internalSelectedApps, app];\n \n setInternalSelectedApps(newSelection);\n onSelectionChange?.(newSelection);\n } else {\n onAppSelected?.({ app, authUrl });\n \n if (!preventDefault) {\n window.open(authUrl, '_blank');\n }\n \n setIsOpen(false);\n setQuery('');\n setResults([]);\n }\n }, [authToken, apiBaseUrl, appAuthPath, multiSelect, internalSelectedApps, onAppSelected, onSelectionChange, preventDefault]);\n\n // Handle keyboard navigation\n const handleKeyDown = useCallback((event: React.KeyboardEvent) => {\n if (!isOpen) return;\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n setSelectedIndex((prev) => Math.min(prev + 1, displayResults.length - 1));\n break;\n case 'ArrowUp':\n event.preventDefault();\n setSelectedIndex((prev) => Math.max(prev - 1, -1));\n break;\n case 'Enter':\n event.preventDefault();\n if (selectedIndex >= 0 && displayResults[selectedIndex]) {\n selectApp(displayResults[selectedIndex]);\n }\n break;\n case 'Escape':\n event.preventDefault();\n setIsOpen(false);\n setSelectedIndex(-1);\n inputRef.current?.blur();\n break;\n }\n }, [isOpen, results, selectedIndex, selectApp]);\n\n // Check if app is selected\n const isAppSelected = useCallback((app: AlgoliaSearchApp) => {\n return internalSelectedApps.some((a) => a.objectID === app.objectID);\n }, [internalSelectedApps]);\n\n // Filter the user's private apps client-side by the current query.\n const filteredPrivateApps = useMemo(() => {\n if (privateApps.length === 0) return [];\n const q = query.trim().toLowerCase();\n if (!q) return privateApps;\n return privateApps.filter(a =>\n a.name.toLowerCase().includes(q) ||\n (a.description || '').toLowerCase().includes(q) ||\n (a.categories || []).some(c => c.toLowerCase().includes(q))\n );\n }, [privateApps, query]);\n\n // Merge private + public apps, apply source filter, prepend pinned, dedupe by name.\n const displayResults = useMemo(() => {\n const norm = (n: string) => (n || '').toLowerCase().replace(/[\\s_\\-]+/g, '');\n\n let merged: AlgoliaSearchApp[];\n if (sourceFilter === 'public') {\n merged = results;\n } else if (sourceFilter === 'private') {\n merged = filteredPrivateApps;\n } else {\n // 'all' — private apps first (your own tools win), then public, deduped by name\n const privateNames = new Set(filteredPrivateApps.map(a => norm(a.name)));\n const publicOnly = results.filter(a => !privateNames.has(norm(a.name)));\n merged = [...filteredPrivateApps, ...publicOnly];\n }\n\n if (!pinnedApps || pinnedApps.length === 0) return merged;\n const pinnedNames = new Set(pinnedApps.map(a => norm(a.name)));\n return [...pinnedApps, ...merged.filter(a => !pinnedNames.has(norm(a.name)))];\n }, [pinnedApps, results, filteredPrivateApps, sourceFilter]);\n\n // Get grid columns style\n const getGridColumnsStyle = useMemo(() => {\n if (layout !== 'grid') return {};\n \n const cols = typeof gridColumns === 'number' ? gridColumns : gridColumns.md || 3;\n return { '--singul-grid-columns': cols } as React.CSSProperties;\n }, [layout, gridColumns]);\n\n // Render individual app item\n const renderAppItem = (app: AlgoliaSearchApp, index: number) => {\n const selected = isAppSelected(app);\n const isHighlighted = index === selectedIndex;\n const authState = getAppAuthState(app);\n\n // Use custom render if provided\n if (renderItem) {\n return (\n <div\n key={app.objectID}\n onClick={() => selectApp(app)}\n onMouseEnter={() => setSelectedIndex(index)}\n >\n {renderItem(app, selected, () => selectApp(app), authState)}\n </div>\n );\n }\n\n // Show both chips (configured/not configured AND tested/not tested)\n return (\n <div\n key={app.objectID}\n className={`singul-dropdown-item ${selected ? 'singul-selected' : ''} ${authState.validated ? 'singul-validated' : ''} ${authState.configured ? 'singul-configured' : ''}`}\n style={{\n ...customStyles.dropdownItem,\n ...(selected ? customStyles.selectedItem : {}),\n }}\n onClick={() => selectApp(app)}\n >\n <div className=\"singul-app-info\" style={customStyles.appInfo}>\n {app.image_url && (\n <div style={{ position: 'relative', flexShrink: 0 }}>\n <img\n src={app.image_url}\n alt={app.name}\n className=\"singul-app-icon\"\n style={customStyles.appIcon}\n />\n {/* Color-coded status dot: green=tested, yellow=configured, blue=activated/selected, gray=inactive */}\n {!hideAuthStatus && (\n <span\n className={`singul-status-dot ${\n authState.validated ? 'singul-dot-validated' :\n authState.configured ? 'singul-dot-configured' :\n selected ? 'singul-dot-activated' :\n 'singul-dot-inactive'\n }`}\n />\n )}\n </div>\n )}\n <div className=\"singul-app-details\" style={customStyles.appDetails}>\n <span className=\"singul-app-name\" style={customStyles.appName}>\n {app.name.replace(/_/g, ' ')}\n {app.source === 'private' && (\n <span className=\"singul-private-badge\" title=\"From your private apps\">Private</span>\n )}\n </span>\n {showDescription && app.description && (\n <span className=\"singul-app-description\" style={customStyles.appDescription}>\n {app.description}\n </span>\n )}\n {showCategories && app.categories?.[0] && (\n <span className=\"singul-app-category\" style={customStyles.appCategory}>\n {app.categories[0]}\n </span>\n )}\n </div>\n {showCheckbox && (\n <div\n className={`singul-checkbox ${selected ? 'singul-checked' : ''}`}\n style={selected ? customStyles.checkboxChecked : customStyles.checkbox}\n >\n {selected && (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"3\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n )}\n </div>\n )}\n </div>\n </div>\n );\n };\n\n return (\n <div\n ref={containerRef}\n className={`singul-container ${inline ? 'singul-inline' : ''} ${className}`}\n style={customStyles.container}\n >\n <div className=\"singul-search-bar-container\">\n <div className=\"singul-search-input-wrapper\" style={customStyles.inputWrapper}>\n <input\n ref={inputRef}\n type=\"text\"\n className=\"singul-search-input\"\n style={customStyles.input}\n placeholder={placeholder}\n value={query}\n onChange={handleInputChange}\n onFocus={() => {\n if (!inline && query.trim() && results.length > 0) {\n setIsOpen(true);\n }\n }}\n onKeyDown={handleKeyDown}\n autoComplete=\"off\"\n />\n <div className=\"singul-search-icon\" style={customStyles.searchIcon}>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.35-4.35\" />\n </svg>\n </div>\n {isLoading && (\n <div className=\"singul-loading-spinner\" style={customStyles.loadingSpinner}>\n {renderLoadingState ? (\n renderLoadingState()\n ) : (\n <div className=\"singul-spinner\" style={customStyles.spinner} />\n )}\n </div>\n )}\n </div>\n\n {/* Source filter — only shown when private apps are loaded */}\n {showSourceFilter && privateApps.length > 0 && (\n <div className=\"singul-source-filter\" role=\"tablist\" aria-label=\"Filter by app source\">\n {([\n { key: 'all', label: 'All', count: results.length + filteredPrivateApps.length },\n { key: 'public', label: 'Public', count: results.length },\n { key: 'private', label: 'Private', count: filteredPrivateApps.length },\n ] as const).map(opt => (\n <button\n key={opt.key}\n type=\"button\"\n role=\"tab\"\n aria-selected={sourceFilter === opt.key}\n className={`singul-source-pill ${sourceFilter === opt.key ? 'singul-source-pill-active' : ''}`}\n onClick={() => setSourceFilter(opt.key)}\n >\n {opt.label}\n <span className=\"singul-source-count\">{opt.count}</span>\n </button>\n ))}\n </div>\n )}\n\n {/* Inline Results Container */}\n {inline && (\n <div\n className={`singul-results-container ${layout === 'grid' ? 'singul-results-grid' : ''}`}\n style={{\n ...customStyles.resultsContainer,\n ...getGridColumnsStyle,\n ...(layout === 'grid' ? { display: 'grid' } : {}),\n }}\n >\n {displayResults.length > 0 ? (\n <>\n {displayResults.map((app, index) => renderAppItem(app, index))}\n <div className=\"singul-end-of-results\" style={{\n padding: '10px 16px',\n color: 'rgba(255, 255, 255, 0.3)',\n textAlign: 'center' as const,\n fontSize: '11px',\n gridColumn: '1 / -1',\n }}>\n Can't find what you're looking for? Try a different search term.\n </div>\n </>\n ) : query.trim() ? (\n <div className=\"singul-empty-state\" style={customStyles.emptyState}>\n {renderEmptyState ? (\n renderEmptyState()\n ) : (\n <>No integrations match \"{query}\". Try searching for a different app or category.</>\n )}\n </div>\n ) : (\n <div className=\"singul-empty-state\" style={customStyles.emptyState}>\n Start typing to search integrations...\n </div>\n )}\n </div>\n )}\n\n {/* Dropdown Results (non-inline mode) */}\n {!inline && isOpen && (\n <div\n className={`singul-dropdown ${layout === 'grid' ? 'singul-dropdown-grid' : ''}`}\n style={{\n ...customStyles.dropdown,\n ...getGridColumnsStyle,\n ...(layout === 'grid' ? { display: 'grid' } : {}),\n }}\n >\n {displayResults.length > 0 ? (\n displayResults.map((app, index) => renderAppItem(app, index))\n ) : (\n <div className=\"singul-empty-state\" style={customStyles.emptyState}>\n {renderEmptyState ? (\n renderEmptyState()\n ) : (\n <>No apps found for \"{query}\"</>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n );\n});\n\nSingulJS.displayName = 'SingulJS';\n\nexport default SingulJS;\n"],"mappings":"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","names":[]}
|