@echothink-ui/search 0.1.0
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 +5 -0
- package/dist/components/AgentSearchSuggestion.d.ts +2 -0
- package/dist/components/AppCommandSearch.d.ts +2 -0
- package/dist/components/AppDomainSearch.d.ts +2 -0
- package/dist/components/EntitySearchInput.d.ts +4 -0
- package/dist/components/GlobalCommandPalette.d.ts +4 -0
- package/dist/components/ProjectCommandPalette.d.ts +2 -0
- package/dist/components/RecentItems.d.ts +2 -0
- package/dist/components/ResourceSearch.d.ts +2 -0
- package/dist/components/SavedSearches.d.ts +2 -0
- package/dist/components/SearchFacetPanel.d.ts +2 -0
- package/dist/components/SearchResultsPanel.d.ts +2 -0
- package/dist/components/SemanticSearchResult.d.ts +2 -0
- package/dist/components/searchUtils.d.ts +3 -0
- package/dist/components/types.d.ts +175 -0
- package/dist/index.cjs +1224 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +1460 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1185 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
- package/src/components/AgentSearchSuggestion.tsx +44 -0
- package/src/components/AppCommandSearch.tsx +163 -0
- package/src/components/AppDomainSearch.tsx +90 -0
- package/src/components/EntitySearchInput.tsx +165 -0
- package/src/components/GlobalCommandPalette.tsx +182 -0
- package/src/components/ProjectCommandPalette.tsx +24 -0
- package/src/components/RecentItems.tsx +136 -0
- package/src/components/ResourceSearch.tsx +27 -0
- package/src/components/SavedSearches.tsx +27 -0
- package/src/components/SearchFacetPanel.tsx +100 -0
- package/src/components/SearchResultsPanel.tsx +327 -0
- package/src/components/SemanticSearchResult.tsx +52 -0
- package/src/components/searchUtils.ts +20 -0
- package/src/components/types.ts +208 -0
- package/src/index.test.tsx +254 -0
- package/src/index.tsx +55 -0
- package/src/styles.css +1716 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
AppCommandSearch,
|
|
5
|
+
ProjectCommandPalette,
|
|
6
|
+
RecentItems,
|
|
7
|
+
ResourceSearch,
|
|
8
|
+
SearchFacetPanel,
|
|
9
|
+
SearchResultsPanel,
|
|
10
|
+
SemanticSearchResult
|
|
11
|
+
} from "./index";
|
|
12
|
+
|
|
13
|
+
const commands = [
|
|
14
|
+
{
|
|
15
|
+
id: "open-approvals",
|
|
16
|
+
label: "Open approval queue",
|
|
17
|
+
description: "Review resource actions waiting on legal.",
|
|
18
|
+
domain: "Workflow",
|
|
19
|
+
kind: "app-domain" as const,
|
|
20
|
+
shortcut: "G A"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "draft-follow-up",
|
|
24
|
+
label: "Draft follow-up",
|
|
25
|
+
description: "Create a message from the selected resource.",
|
|
26
|
+
domain: "Inbox",
|
|
27
|
+
kind: "action" as const
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const paletteCommands = [
|
|
32
|
+
{
|
|
33
|
+
id: "open-approvals",
|
|
34
|
+
label: "Open approval queue",
|
|
35
|
+
group: "Navigation",
|
|
36
|
+
hint: "Review resource actions waiting on legal.",
|
|
37
|
+
onSelect: () => undefined
|
|
38
|
+
}
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
describe("@echothink-ui/search", () => {
|
|
42
|
+
it("renders command search affordances and scoped command results", () => {
|
|
43
|
+
render(
|
|
44
|
+
<AppCommandSearch value="approval" scopeLabel="Workspace domains" commands={commands} />
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(screen.getByDisplayValue("approval")).toBeTruthy();
|
|
48
|
+
expect(screen.getByText("⌘K")).toBeTruthy();
|
|
49
|
+
expect(screen.getByText("Workspace domains")).toBeTruthy();
|
|
50
|
+
expect(screen.getByText("Open approval queue")).toBeTruthy();
|
|
51
|
+
expect(screen.getByText("Workflow")).toBeTruthy();
|
|
52
|
+
expect(screen.getByRole("button", { name: "Run" })).toBeTruthy();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("selects the active command on submit", () => {
|
|
56
|
+
const onSelect = vi.fn();
|
|
57
|
+
const { container } = render(
|
|
58
|
+
<AppCommandSearch value="approval" commands={commands} onSelect={onSelect} />
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
fireEvent.submit(container.querySelector("form")!);
|
|
62
|
+
|
|
63
|
+
expect(onSelect).toHaveBeenCalledWith("open-approvals");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("labels the project scoped command palette with its project context", () => {
|
|
67
|
+
render(
|
|
68
|
+
<ProjectCommandPalette
|
|
69
|
+
open
|
|
70
|
+
onClose={vi.fn()}
|
|
71
|
+
projectRef="marketing"
|
|
72
|
+
commands={paletteCommands}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
expect(screen.getByRole("dialog", { name: "Project command palette" })).toBeTruthy();
|
|
77
|
+
expect(screen.getByText("Project marketing")).toBeTruthy();
|
|
78
|
+
expect(screen.getByText("Open approval queue")).toBeTruthy();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("renders grouped search results with metadata and row selection", () => {
|
|
82
|
+
const onSelect = vi.fn();
|
|
83
|
+
const { rerender } = render(
|
|
84
|
+
<SearchResultsPanel
|
|
85
|
+
query="brief"
|
|
86
|
+
groups={[
|
|
87
|
+
{
|
|
88
|
+
id: "tasks",
|
|
89
|
+
label: "Tasks",
|
|
90
|
+
results: [
|
|
91
|
+
{
|
|
92
|
+
id: "t1",
|
|
93
|
+
label: "Approve Q3 brief",
|
|
94
|
+
description: "Campaign task waiting on final signoff",
|
|
95
|
+
owner: "Jane",
|
|
96
|
+
status: "Due today"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "t2",
|
|
100
|
+
label: "Draft pricing copy",
|
|
101
|
+
description: "Matched by launch brief"
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: "docs",
|
|
107
|
+
label: "Documents",
|
|
108
|
+
results: [
|
|
109
|
+
{
|
|
110
|
+
id: "d1",
|
|
111
|
+
label: "Brief.md",
|
|
112
|
+
version: 12,
|
|
113
|
+
updatedAt: "2h ago"
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
]}
|
|
118
|
+
onSelect={onSelect}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
expect(screen.getByText("3 results")).toBeTruthy();
|
|
123
|
+
expect(screen.getByText("\"brief\"")).toBeTruthy();
|
|
124
|
+
expect(screen.getByText("Tasks")).toBeTruthy();
|
|
125
|
+
expect(screen.getByText("Campaign task waiting on final signoff")).toBeTruthy();
|
|
126
|
+
expect(screen.getByText("Owner: Jane")).toBeTruthy();
|
|
127
|
+
expect(screen.getByText("Due today")).toBeTruthy();
|
|
128
|
+
expect(screen.getByText("v12")).toBeTruthy();
|
|
129
|
+
|
|
130
|
+
fireEvent.click(screen.getByRole("button", { name: /approve q3 brief/i }));
|
|
131
|
+
|
|
132
|
+
expect(onSelect).toHaveBeenCalledWith("t1");
|
|
133
|
+
|
|
134
|
+
rerender(
|
|
135
|
+
<SearchResultsPanel query="missing" groups={[]} emptyLabel="Nothing found" />
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
expect(screen.getByText("Nothing found")).toBeTruthy();
|
|
139
|
+
expect(screen.getByText("Try another keyword or remove a filter.")).toBeTruthy();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("renders recent items with row-level actions and an empty state", () => {
|
|
143
|
+
const onSelect = vi.fn();
|
|
144
|
+
const { rerender } = render(
|
|
145
|
+
<RecentItems
|
|
146
|
+
items={[
|
|
147
|
+
{
|
|
148
|
+
id: "brief",
|
|
149
|
+
label: "Brief.md",
|
|
150
|
+
kind: "document",
|
|
151
|
+
description: "Updated project kickoff notes",
|
|
152
|
+
visitedAt: "10:42"
|
|
153
|
+
}
|
|
154
|
+
]}
|
|
155
|
+
onSelect={onSelect}
|
|
156
|
+
/>
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
fireEvent.click(screen.getByRole("button", { name: /open brief\.md/i }));
|
|
160
|
+
|
|
161
|
+
expect(onSelect).toHaveBeenCalledWith("brief");
|
|
162
|
+
expect(screen.getByText("Document")).toBeTruthy();
|
|
163
|
+
expect(screen.getByText("Updated project kickoff notes")).toBeTruthy();
|
|
164
|
+
expect(screen.getByText("10:42")).toBeTruthy();
|
|
165
|
+
|
|
166
|
+
rerender(<RecentItems items={[]} />);
|
|
167
|
+
|
|
168
|
+
expect(screen.getByText("No recent items")).toBeTruthy();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("renders resource search with resource-specific suggestion semantics", () => {
|
|
172
|
+
const { container } = render(
|
|
173
|
+
<ResourceSearch
|
|
174
|
+
open
|
|
175
|
+
resourceScope="project:marketing"
|
|
176
|
+
suggestions={[
|
|
177
|
+
{
|
|
178
|
+
id: "brief",
|
|
179
|
+
label: "Launch brief",
|
|
180
|
+
kind: "Document",
|
|
181
|
+
meta: "Synced 12 min ago"
|
|
182
|
+
}
|
|
183
|
+
]}
|
|
184
|
+
/>
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
expect(container.firstElementChild?.getAttribute("data-eth-component")).toBe(
|
|
188
|
+
"ResourceSearch"
|
|
189
|
+
);
|
|
190
|
+
expect(container.firstElementChild?.getAttribute("data-resource-scope")).toBe(
|
|
191
|
+
"project:marketing"
|
|
192
|
+
);
|
|
193
|
+
expect(screen.getByRole("listbox", { name: "Resource suggestions" })).toBeTruthy();
|
|
194
|
+
expect(screen.getByText("Resource matches")).toBeTruthy();
|
|
195
|
+
expect(screen.getByText("Launch brief")).toBeTruthy();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("renders selected search facets with a clear action", () => {
|
|
199
|
+
const onChange = vi.fn();
|
|
200
|
+
|
|
201
|
+
render(
|
|
202
|
+
<SearchFacetPanel
|
|
203
|
+
facets={[
|
|
204
|
+
{
|
|
205
|
+
id: "kind",
|
|
206
|
+
label: "Kind",
|
|
207
|
+
options: [
|
|
208
|
+
{ value: "task", label: "Task", count: 12 },
|
|
209
|
+
{ value: "doc", label: "Document", count: 4 }
|
|
210
|
+
]
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: "status",
|
|
214
|
+
label: "Status",
|
|
215
|
+
options: [{ value: "open", label: "Open", count: 10 }]
|
|
216
|
+
}
|
|
217
|
+
]}
|
|
218
|
+
selected={{ kind: ["task"], status: ["open"] }}
|
|
219
|
+
onChange={onChange}
|
|
220
|
+
/>
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
expect(screen.getByRole("complementary", { name: "Search filters" })).toBeTruthy();
|
|
224
|
+
expect(screen.getByText("2 selected")).toBeTruthy();
|
|
225
|
+
expect(screen.getByText("Task")).toBeTruthy();
|
|
226
|
+
expect(screen.getByText("12")).toBeTruthy();
|
|
227
|
+
|
|
228
|
+
fireEvent.click(screen.getByRole("button", { name: "Clear" }));
|
|
229
|
+
|
|
230
|
+
expect(onChange).toHaveBeenCalledWith({ kind: [], status: [] });
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("renders semantic search snippets with highlighted evidence", () => {
|
|
234
|
+
const { container } = render(
|
|
235
|
+
<SemanticSearchResult
|
|
236
|
+
result={{
|
|
237
|
+
id: "policy",
|
|
238
|
+
snippet: "Approval policy requires legal review for external sends.",
|
|
239
|
+
highlights: [[0, 17]],
|
|
240
|
+
evidence: [
|
|
241
|
+
{ id: "policy-v3", label: "policy-v3.md", href: "#" },
|
|
242
|
+
{ id: "incident-review", label: "Q2 incident review" }
|
|
243
|
+
]
|
|
244
|
+
}}
|
|
245
|
+
/>
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
expect(container.querySelector("mark")?.textContent).toBe("Approval policy");
|
|
249
|
+
expect(screen.getByText("Approval policy")).toBeTruthy();
|
|
250
|
+
expect(screen.getByText("Evidence")).toBeTruthy();
|
|
251
|
+
expect(screen.getByRole("link", { name: "policy-v3.md" })).toBeTruthy();
|
|
252
|
+
expect(screen.getByText("Q2 incident review")).toBeTruthy();
|
|
253
|
+
});
|
|
254
|
+
});
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import "./styles.css";
|
|
2
|
+
|
|
3
|
+
export type {
|
|
4
|
+
AgentSearchSuggestionProps,
|
|
5
|
+
AppCommandItem,
|
|
6
|
+
AppCommandKind,
|
|
7
|
+
AppCommandSearchProps,
|
|
8
|
+
AppDomainSearchProps,
|
|
9
|
+
CommandItem,
|
|
10
|
+
EntitySearchInputProps,
|
|
11
|
+
EntitySuggestion,
|
|
12
|
+
GlobalCommandPaletteProps,
|
|
13
|
+
ProjectCommandPaletteProps,
|
|
14
|
+
RecentItem,
|
|
15
|
+
RecentItemsProps,
|
|
16
|
+
ResourceSearchProps,
|
|
17
|
+
SavedSearch,
|
|
18
|
+
SavedSearchesProps,
|
|
19
|
+
SearchFacet,
|
|
20
|
+
SearchFacetOption,
|
|
21
|
+
SearchFacetPanelProps,
|
|
22
|
+
SearchResultGroup,
|
|
23
|
+
SearchResultItem,
|
|
24
|
+
SearchResultMetadata,
|
|
25
|
+
SearchResultsPanelProps,
|
|
26
|
+
SemanticSearchResultProps
|
|
27
|
+
} from "./components/types";
|
|
28
|
+
export { AgentSearchSuggestion } from "./components/AgentSearchSuggestion";
|
|
29
|
+
export { AppCommandSearch } from "./components/AppCommandSearch";
|
|
30
|
+
export { AppDomainSearch } from "./components/AppDomainSearch";
|
|
31
|
+
export { EntitySearchInput } from "./components/EntitySearchInput";
|
|
32
|
+
export { GlobalCommandPalette } from "./components/GlobalCommandPalette";
|
|
33
|
+
export { ProjectCommandPalette } from "./components/ProjectCommandPalette";
|
|
34
|
+
export { RecentItems } from "./components/RecentItems";
|
|
35
|
+
export { ResourceSearch } from "./components/ResourceSearch";
|
|
36
|
+
export { SavedSearches } from "./components/SavedSearches";
|
|
37
|
+
export { SearchFacetPanel } from "./components/SearchFacetPanel";
|
|
38
|
+
export { SearchResultsPanel } from "./components/SearchResultsPanel";
|
|
39
|
+
export { SemanticSearchResult } from "./components/SemanticSearchResult";
|
|
40
|
+
|
|
41
|
+
export const SearchComponentNames = [
|
|
42
|
+
"GlobalCommandPalette",
|
|
43
|
+
"ProjectCommandPalette",
|
|
44
|
+
"AppCommandSearch",
|
|
45
|
+
"SearchResultsPanel",
|
|
46
|
+
"SearchFacetPanel",
|
|
47
|
+
"SavedSearches",
|
|
48
|
+
"RecentItems",
|
|
49
|
+
"EntitySearchInput",
|
|
50
|
+
"AppDomainSearch",
|
|
51
|
+
"ResourceSearch",
|
|
52
|
+
"SemanticSearchResult",
|
|
53
|
+
"AgentSearchSuggestion"
|
|
54
|
+
] as const;
|
|
55
|
+
export type SearchComponentName = (typeof SearchComponentNames)[number];
|