@umbra.ui/core 0.2.0 → 0.4.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/dist/components/controls/InlineDropdown/InlineDropdown.vue +290 -0
- package/dist/components/controls/InlineDropdown/README.md +35 -0
- package/dist/components/controls/InlineDropdown/theme.css +40 -0
- package/dist/components/dialogs/Alert/Alert.vue +122 -11
- package/dist/components/dialogs/Alert/theme.css +20 -0
- package/dist/components/dialogs/Toast/useToast.d.ts +1 -1
- package/dist/components/inputs/AutogrowRichTextView/AutogrowRichTextView.vue +128 -0
- package/dist/components/inputs/AutogrowRichTextView/README.md +86 -0
- package/dist/components/inputs/AutogrowRichTextView/editor.css +211 -0
- package/dist/components/inputs/AutogrowRichTextView/theme.css +28 -0
- package/dist/components/inputs/InputCryptoAddress/InputCryptoAddress.vue +512 -0
- package/dist/components/inputs/InputCryptoAddress/README.md +45 -0
- package/dist/components/inputs/InputCryptoAddress/theme.css +80 -0
- package/dist/components/inputs/Tags/TagBar.vue +7 -4
- package/dist/components/inputs/Tags/theme.css +4 -0
- package/dist/components/inputs/search/README.md +64 -736
- package/dist/components/inputs/search/SearchOverlay.vue +376 -0
- package/dist/components/inputs/search/SearchResultCell.vue +205 -0
- package/dist/components/inputs/search/theme.css +66 -21
- package/dist/components/inputs/search/types.d.ts +27 -5
- package/dist/components/inputs/search/types.d.ts.map +1 -1
- package/dist/components/inputs/search/types.ts +33 -5
- package/dist/components/menus/ActionMenu/ActionMenu.vue +29 -7
- package/dist/components/menus/ActionMenu/theme.css +1 -1
- package/dist/components/menus/ActionMenu/types.d.ts +9 -0
- package/dist/components/menus/ActionMenu/types.d.ts.map +1 -0
- package/dist/components/menus/ActionMenu/types.js +1 -0
- package/dist/components/menus/ActionMenu/types.ts +9 -0
- package/dist/components/models/Popover/Popover.vue +6 -84
- package/dist/css/umbra-ui.css +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/package.json +21 -16
- package/src/components/controls/InlineDropdown/InlineDropdown.vue +290 -0
- package/src/components/controls/InlineDropdown/README.md +35 -0
- package/src/components/controls/InlineDropdown/theme.css +40 -0
- package/src/components/dialogs/Alert/Alert.vue +122 -11
- package/src/components/dialogs/Alert/theme.css +20 -0
- package/src/components/inputs/AutogrowRichTextView/AutogrowRichTextView.vue +128 -0
- package/src/components/inputs/AutogrowRichTextView/README.md +86 -0
- package/src/components/inputs/AutogrowRichTextView/editor.css +211 -0
- package/src/components/inputs/AutogrowRichTextView/theme.css +28 -0
- package/src/components/inputs/InputCryptoAddress/InputCryptoAddress.vue +512 -0
- package/src/components/inputs/InputCryptoAddress/README.md +45 -0
- package/src/components/inputs/InputCryptoAddress/theme.css +80 -0
- package/src/components/inputs/Tags/TagBar.vue +7 -4
- package/src/components/inputs/Tags/theme.css +4 -0
- package/src/components/inputs/search/README.md +64 -736
- package/src/components/inputs/search/SearchOverlay.vue +376 -0
- package/src/components/inputs/search/SearchResultCell.vue +205 -0
- package/src/components/inputs/search/theme.css +66 -21
- package/src/components/inputs/search/types.ts +33 -5
- package/src/components/menus/ActionMenu/ActionMenu.vue +29 -7
- package/src/components/menus/ActionMenu/theme.css +1 -1
- package/src/components/menus/ActionMenu/types.ts +9 -0
- package/src/components/models/Popover/Popover.vue +6 -84
- package/src/index.ts +13 -3
- package/src/vue.d.ts +7 -26
- package/src/components/inputs/search/SearchBar.vue +0 -394
- package/src/components/inputs/search/SearchResults.vue +0 -310
|
@@ -1,759 +1,87 @@
|
|
|
1
|
-
# Search Components
|
|
1
|
+
# Search Overlay Components
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Reusable search UI components that can be dropped into any app.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Components
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import type { SearchResult } from "@umbra-ui/core";
|
|
10
|
-
```
|
|
7
|
+
- `SearchOverlay.vue`: Generic, Teleport-based search overlay with keyboard handling.
|
|
8
|
+
- `SearchResultCell.vue`: Default result row renderer with highlighted matches and clickable path segments.
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
## Quick Integration (AI Agent)
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
1. Render `SearchOverlay` in the view that should host search.
|
|
13
|
+
2. Pass `modelValue`, `query`, `results`, and `activeIndex`.
|
|
14
|
+
3. Use the `result` slot to render rows (use `SearchResultCell` or your own renderer).
|
|
15
|
+
4. Handle emitted events to update state and selection.
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
### Example
|
|
20
18
|
|
|
21
19
|
```vue
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const handleSearchFocus = () => {
|
|
45
|
-
console.log("Search focused");
|
|
46
|
-
};
|
|
47
|
-
</script>
|
|
48
|
-
|
|
49
|
-
<template>
|
|
50
|
-
<div>
|
|
51
|
-
<SearchBar
|
|
52
|
-
v-model:query="searchQuery"
|
|
53
|
-
:items="searchItems"
|
|
54
|
-
type="documents"
|
|
55
|
-
placeholder="Search documents..."
|
|
56
|
-
@onSelect="handleSearchSelect"
|
|
57
|
-
@onFocus="handleSearchFocus"
|
|
20
|
+
<SearchOverlay
|
|
21
|
+
:model-value="isOpen"
|
|
22
|
+
:query="query"
|
|
23
|
+
:results="results"
|
|
24
|
+
:active-index="activeIndex"
|
|
25
|
+
:show-recent="showRecent"
|
|
26
|
+
:search-label="searchLabel"
|
|
27
|
+
:results-label="resultsLabel"
|
|
28
|
+
:can-load-more="canLoadMore"
|
|
29
|
+
@update:model-value="(value) => (isOpen = value)"
|
|
30
|
+
@update:query="(value) => (query = value)"
|
|
31
|
+
@update:active-index="(value) => (activeIndex = value)"
|
|
32
|
+
@select="selectResult"
|
|
33
|
+
@load-more="loadMore"
|
|
34
|
+
>
|
|
35
|
+
<template #result="{ result, query, isActive }">
|
|
36
|
+
<SearchResultCell
|
|
37
|
+
:result="result"
|
|
38
|
+
:query="query"
|
|
39
|
+
:is-active="isActive"
|
|
40
|
+
@segment-click="handleSegmentClick"
|
|
58
41
|
/>
|
|
59
|
-
</
|
|
60
|
-
</
|
|
42
|
+
</template>
|
|
43
|
+
</SearchOverlay>
|
|
61
44
|
```
|
|
62
45
|
|
|
63
|
-
##
|
|
64
|
-
|
|
65
|
-
### SearchBar
|
|
66
|
-
|
|
67
|
-
The main search input component with dropdown results.
|
|
68
|
-
|
|
69
|
-
#### Props
|
|
70
|
-
|
|
71
|
-
| Prop Name | Type | Required | Default | Description |
|
|
72
|
-
| ------------- | ---------------- | -------- | ---------- | ------------------------------------------ |
|
|
73
|
-
| `type` | `string` | No | `"items"` | Type of items being searched (for display) |
|
|
74
|
-
| `query` | `string` | No | `""` | The search query value |
|
|
75
|
-
| `items` | `SearchResult[]` | No | `[]` | Array of searchable items |
|
|
76
|
-
| `placeholder` | `string` | No | `"Search"` | Placeholder text for the search input |
|
|
77
|
-
|
|
78
|
-
#### Events
|
|
46
|
+
## Props (SearchOverlay)
|
|
79
47
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
48
|
+
- `modelValue`: show/hide the overlay.
|
|
49
|
+
- `query`: current input value.
|
|
50
|
+
- `results`: array of `SearchOverlayResult`.
|
|
51
|
+
- `activeIndex`: keyboard selection index.
|
|
52
|
+
- `showRecent`: true to show the “Recent Searches” header.
|
|
53
|
+
- `searchLabel`, `resultsLabel`: text for the header row.
|
|
54
|
+
- `placeholder`: input placeholder text.
|
|
55
|
+
- `recentEmptyLabel`, `resultsEmptyLabel`: empty state copy.
|
|
56
|
+
- `canLoadMore`: shows the “Load More” button.
|
|
85
57
|
|
|
86
|
-
|
|
58
|
+
## Events (SearchOverlay)
|
|
87
59
|
|
|
88
|
-
|
|
60
|
+
- `update:modelValue`
|
|
61
|
+
- `update:query`
|
|
62
|
+
- `update:activeIndex`
|
|
63
|
+
- `select` (row click or enter key)
|
|
64
|
+
- `load-more` (Load More button)
|
|
89
65
|
|
|
90
|
-
|
|
66
|
+
## Result Shape
|
|
91
67
|
|
|
92
|
-
|
|
93
|
-
| --------- | ---------------- | -------- | ---------------------------- |
|
|
94
|
-
| `type` | `string` | Yes | Type of items being searched |
|
|
95
|
-
| `query` | `string` | Yes | Current search query |
|
|
96
|
-
| `matches` | `SearchResult[]` | Yes | Filtered search results |
|
|
68
|
+
The overlay expects `SearchOverlayResult`:
|
|
97
69
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
| Event Name | Payload Type | Description |
|
|
101
|
-
| ---------- | -------------- | ---------------------------------------- |
|
|
102
|
-
| `onSelect` | `SearchResult` | Emitted when a search result is selected |
|
|
103
|
-
|
|
104
|
-
## SearchResult Interface
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
|
-
interface SearchResult {
|
|
70
|
+
```ts
|
|
71
|
+
type SearchOverlayResult = {
|
|
108
72
|
id: string;
|
|
109
|
-
|
|
73
|
+
path: string;
|
|
74
|
+
pathSegments: Array<{
|
|
75
|
+
id: string;
|
|
76
|
+
type: "workspace" | "notebook" | "note" | "collection";
|
|
77
|
+
title: string;
|
|
78
|
+
}>;
|
|
110
79
|
title: string;
|
|
111
|
-
|
|
112
|
-
caption: string;
|
|
113
|
-
footnote: string;
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## CSS Customization
|
|
118
|
-
|
|
119
|
-
The search components use CSS custom properties for theming and customization:
|
|
120
|
-
|
|
121
|
-
### SearchBar Variables
|
|
122
|
-
|
|
123
|
-
```css
|
|
124
|
-
/* Search Bar Colors */
|
|
125
|
-
--search-bar-bg: var(--search-bar-background);
|
|
126
|
-
--search-bar-text: var(--search-bar-text-color);
|
|
127
|
-
--search-bar-placeholder: var(--search-bar-placeholder-color);
|
|
128
|
-
--search-bar-border: var(--search-bar-border-color);
|
|
129
|
-
--search-bar-focus-border: var(--search-bar-focus-border-color);
|
|
130
|
-
--search-bar-shadow: var(--search-bar-shadow-color);
|
|
131
|
-
--search-bar-inset-shadow: var(--search-bar-inset-shadow-color);
|
|
132
|
-
|
|
133
|
-
/* Search Overlay */
|
|
134
|
-
--search-overlay-bg: var(--search-overlay-background);
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### SearchResults Variables
|
|
138
|
-
|
|
139
|
-
```css
|
|
140
|
-
/* Results Container */
|
|
141
|
-
--search-results-bg: var(--search-results-background);
|
|
142
|
-
--search-results-border: var(--search-results-border-color);
|
|
143
|
-
--search-results-shadow: var(--search-results-shadow-color);
|
|
144
|
-
--search-results-inset-shadow: var(--search-results-inset-shadow-color);
|
|
145
|
-
|
|
146
|
-
/* Subheader */
|
|
147
|
-
--search-subheader-bg: var(--search-subheader-background);
|
|
148
|
-
--search-subheader-border: var(--search-subheader-border-color);
|
|
149
|
-
--search-subheader-text: var(--search-subheader-text-color);
|
|
150
|
-
|
|
151
|
-
/* Result Items */
|
|
152
|
-
--search-result-bg: var(--search-result-background);
|
|
153
|
-
--search-result-hover-bg: var(--search-result-hover-background);
|
|
154
|
-
--search-result-border: var(--search-result-border-color);
|
|
155
|
-
--search-result-text: var(--search-result-text-color);
|
|
156
|
-
|
|
157
|
-
/* Icons */
|
|
158
|
-
--search-icon-bg: var(--search-icon-background);
|
|
159
|
-
--search-icon-border: var(--search-icon-border-color);
|
|
160
|
-
|
|
161
|
-
/* Footnotes */
|
|
162
|
-
--search-footnote-bg: var(--search-footnote-background);
|
|
163
|
-
|
|
164
|
-
/* Empty State */
|
|
165
|
-
--search-nothing-text: var(--search-nothing-text-color);
|
|
166
|
-
|
|
167
|
-
/* Load More */
|
|
168
|
-
--search-load-more-bg: var(--search-load-more-background);
|
|
169
|
-
--search-load-more-hover-bg: var(--search-load-more-hover-background);
|
|
170
|
-
--search-load-more-text: var(--search-load-more-text-color);
|
|
171
|
-
|
|
172
|
-
/* Highlighting */
|
|
173
|
-
--search-highlight-bg: var(--search-highlight-background);
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## Key Features
|
|
177
|
-
|
|
178
|
-
### Search Functionality
|
|
179
|
-
|
|
180
|
-
- **Real-time Filtering**: Results update as you type
|
|
181
|
-
- **Multi-field Search**: Searches across title, description, caption, and footnote
|
|
182
|
-
- **Case-insensitive**: Search is not case-sensitive
|
|
183
|
-
- **Highlighting**: Query terms are highlighted in results
|
|
184
|
-
|
|
185
|
-
### User Experience
|
|
186
|
-
|
|
187
|
-
- **Animated Dropdown**: Smooth animations for showing/hiding results
|
|
188
|
-
- **Floating Positioning**: Results are positioned using Floating UI
|
|
189
|
-
- **Keyboard Navigation**: Full keyboard support with escape to close
|
|
190
|
-
- **Click Outside**: Clicking outside closes the search results
|
|
191
|
-
- **Loading States**: Shows loading indicator while building search index
|
|
192
|
-
|
|
193
|
-
### Results Display
|
|
194
|
-
|
|
195
|
-
- **Pagination**: Load more results with pagination
|
|
196
|
-
- **Rich Content**: Support for icons, titles, descriptions, captions, and footnotes
|
|
197
|
-
- **Icon Support**: Both emoji and image icons
|
|
198
|
-
- **Hover Effects**: Visual feedback on result hover
|
|
199
|
-
- **Empty States**: Proper handling of no results
|
|
200
|
-
|
|
201
|
-
### Accessibility
|
|
202
|
-
|
|
203
|
-
- **ARIA Labels**: Proper labeling for screen readers
|
|
204
|
-
- **Keyboard Navigation**: Full keyboard support
|
|
205
|
-
- **Focus Management**: Proper focus handling
|
|
206
|
-
- **Screen Reader Support**: Accessible result announcements
|
|
207
|
-
|
|
208
|
-
## Examples
|
|
209
|
-
|
|
210
|
-
### Basic Search
|
|
211
|
-
|
|
212
|
-
```vue
|
|
213
|
-
<script setup lang="ts">
|
|
214
|
-
import { ref } from "vue";
|
|
215
|
-
import { SearchBar } from "@umbra-ui/core";
|
|
216
|
-
import type { SearchResult } from "@umbra-ui/core";
|
|
217
|
-
|
|
218
|
-
const searchQuery = ref("");
|
|
219
|
-
const searchItems = ref<SearchResult[]>([
|
|
220
|
-
{
|
|
221
|
-
id: "1",
|
|
222
|
-
title: "Getting Started Guide",
|
|
223
|
-
description: "Learn how to get started with our platform",
|
|
224
|
-
caption: "Documentation",
|
|
225
|
-
footnote: "Guide",
|
|
226
|
-
icon: "📚",
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
id: "2",
|
|
230
|
-
title: "API Reference",
|
|
231
|
-
description: "Complete API documentation and examples",
|
|
232
|
-
caption: "Documentation",
|
|
233
|
-
footnote: "API",
|
|
234
|
-
icon: "🔧",
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
id: "3",
|
|
238
|
-
title: "User Manual",
|
|
239
|
-
description: "Detailed user manual with step-by-step instructions",
|
|
240
|
-
caption: "Documentation",
|
|
241
|
-
footnote: "Manual",
|
|
242
|
-
icon: "📖",
|
|
243
|
-
},
|
|
244
|
-
]);
|
|
245
|
-
|
|
246
|
-
const handleSearchSelect = (result: SearchResult) => {
|
|
247
|
-
console.log("Selected:", result);
|
|
248
|
-
searchQuery.value = result.title;
|
|
80
|
+
excerpt: string;
|
|
249
81
|
};
|
|
250
|
-
</script>
|
|
251
|
-
|
|
252
|
-
<template>
|
|
253
|
-
<div class="basic-search">
|
|
254
|
-
<h3>Documentation Search</h3>
|
|
255
|
-
|
|
256
|
-
<SearchBar
|
|
257
|
-
v-model:query="searchQuery"
|
|
258
|
-
:items="searchItems"
|
|
259
|
-
type="documents"
|
|
260
|
-
placeholder="Search documentation..."
|
|
261
|
-
@onSelect="handleSearchSelect"
|
|
262
|
-
/>
|
|
263
|
-
|
|
264
|
-
<div v-if="searchQuery" class="search-info">
|
|
265
|
-
<p>Searching for: "{{ searchQuery }}"</p>
|
|
266
|
-
</div>
|
|
267
|
-
</div>
|
|
268
|
-
</template>
|
|
269
|
-
|
|
270
|
-
<style module>
|
|
271
|
-
.basic-search {
|
|
272
|
-
max-width: 500px;
|
|
273
|
-
padding: 2rem;
|
|
274
|
-
border: 1px solid #e0e0e0;
|
|
275
|
-
border-radius: 0.5rem;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
.search-info {
|
|
279
|
-
margin-top: 1rem;
|
|
280
|
-
padding: 1rem;
|
|
281
|
-
background-color: #f8f9fa;
|
|
282
|
-
border-radius: 0.25rem;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
.search-info p {
|
|
286
|
-
margin: 0;
|
|
287
|
-
color: #666;
|
|
288
|
-
font-size: 0.875rem;
|
|
289
|
-
}
|
|
290
|
-
</style>
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
### Advanced Search with Custom Data
|
|
294
|
-
|
|
295
|
-
```vue
|
|
296
|
-
<script setup lang="ts">
|
|
297
|
-
import { ref, computed } from "vue";
|
|
298
|
-
import { SearchBar } from "@umbra-ui/core";
|
|
299
|
-
import type { SearchResult } from "@umbra-ui/core";
|
|
300
|
-
|
|
301
|
-
const searchQuery = ref("");
|
|
302
|
-
const allItems = ref<SearchResult[]>([
|
|
303
|
-
{
|
|
304
|
-
id: "1",
|
|
305
|
-
title: "Vue.js Framework",
|
|
306
|
-
description:
|
|
307
|
-
"Progressive JavaScript framework for building user interfaces",
|
|
308
|
-
caption: "Framework",
|
|
309
|
-
footnote: "JavaScript",
|
|
310
|
-
icon: "🟢",
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
id: "2",
|
|
314
|
-
title: "React Library",
|
|
315
|
-
description: "A JavaScript library for building user interfaces",
|
|
316
|
-
caption: "Library",
|
|
317
|
-
footnote: "JavaScript",
|
|
318
|
-
icon: "⚛️",
|
|
319
|
-
},
|
|
320
|
-
{
|
|
321
|
-
id: "3",
|
|
322
|
-
title: "Angular Platform",
|
|
323
|
-
description: "Platform for building mobile and desktop web applications",
|
|
324
|
-
caption: "Platform",
|
|
325
|
-
footnote: "TypeScript",
|
|
326
|
-
icon: "🔴",
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
id: "4",
|
|
330
|
-
title: "Svelte Compiler",
|
|
331
|
-
description:
|
|
332
|
-
"Cybernetically enhanced web apps with compile-time optimizations",
|
|
333
|
-
caption: "Compiler",
|
|
334
|
-
footnote: "JavaScript",
|
|
335
|
-
icon: "🧡",
|
|
336
|
-
},
|
|
337
|
-
]);
|
|
338
|
-
|
|
339
|
-
const filteredItems = computed(() => {
|
|
340
|
-
if (!searchQuery.value.trim()) {
|
|
341
|
-
return allItems.value;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const query = searchQuery.value.toLowerCase();
|
|
345
|
-
return allItems.value.filter((item) => {
|
|
346
|
-
const searchText =
|
|
347
|
-
`${item.title} ${item.description} ${item.caption} ${item.footnote}`.toLowerCase();
|
|
348
|
-
return searchText.includes(query);
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
const handleSearchSelect = (result: SearchResult) => {
|
|
353
|
-
console.log("Selected framework:", result);
|
|
354
|
-
searchQuery.value = result.title;
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
const handleSearchFocus = () => {
|
|
358
|
-
console.log("Search focused - showing all items");
|
|
359
|
-
};
|
|
360
|
-
</script>
|
|
361
|
-
|
|
362
|
-
<template>
|
|
363
|
-
<div class="advanced-search">
|
|
364
|
-
<h3>Framework Search</h3>
|
|
365
|
-
|
|
366
|
-
<SearchBar
|
|
367
|
-
v-model:query="searchQuery"
|
|
368
|
-
:items="filteredItems"
|
|
369
|
-
type="frameworks"
|
|
370
|
-
placeholder="Search JavaScript frameworks..."
|
|
371
|
-
@onSelect="handleSearchSelect"
|
|
372
|
-
@onFocus="handleSearchFocus"
|
|
373
|
-
/>
|
|
374
|
-
|
|
375
|
-
<div class="search-stats">
|
|
376
|
-
<p>Total items: {{ allItems.length }}</p>
|
|
377
|
-
<p v-if="searchQuery">Filtered results: {{ filteredItems.length }}</p>
|
|
378
|
-
</div>
|
|
379
|
-
</div>
|
|
380
|
-
</template>
|
|
381
|
-
|
|
382
|
-
<style module>
|
|
383
|
-
.advanced-search {
|
|
384
|
-
max-width: 600px;
|
|
385
|
-
padding: 2rem;
|
|
386
|
-
border: 1px solid #e0e0e0;
|
|
387
|
-
border-radius: 0.5rem;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
.search-stats {
|
|
391
|
-
margin-top: 1rem;
|
|
392
|
-
padding: 1rem;
|
|
393
|
-
background-color: #f8f9fa;
|
|
394
|
-
border-radius: 0.25rem;
|
|
395
|
-
display: flex;
|
|
396
|
-
gap: 2rem;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
.search-stats p {
|
|
400
|
-
margin: 0;
|
|
401
|
-
color: #666;
|
|
402
|
-
font-size: 0.875rem;
|
|
403
|
-
}
|
|
404
|
-
</style>
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
### Search with API Integration
|
|
408
|
-
|
|
409
|
-
```vue
|
|
410
|
-
<script setup lang="ts">
|
|
411
|
-
import { ref, watch } from "vue";
|
|
412
|
-
import { SearchBar } from "@umbra-ui/core";
|
|
413
|
-
import type { SearchResult } from "@umbra-ui/core";
|
|
414
|
-
|
|
415
|
-
const searchQuery = ref("");
|
|
416
|
-
const searchItems = ref<SearchResult[]>([]);
|
|
417
|
-
const isLoading = ref(false);
|
|
418
|
-
const error = ref("");
|
|
419
|
-
|
|
420
|
-
const searchAPI = async (query: string) => {
|
|
421
|
-
if (!query.trim()) {
|
|
422
|
-
searchItems.value = [];
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
isLoading.value = true;
|
|
427
|
-
error.value = "";
|
|
428
|
-
|
|
429
|
-
try {
|
|
430
|
-
// Simulate API call
|
|
431
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
432
|
-
|
|
433
|
-
// Simulate API response
|
|
434
|
-
const mockResults: SearchResult[] = [
|
|
435
|
-
{
|
|
436
|
-
id: "1",
|
|
437
|
-
title: `Search Result for "${query}"`,
|
|
438
|
-
description: `This is a mock result for the query "${query}"`,
|
|
439
|
-
caption: "API Result",
|
|
440
|
-
footnote: "Mock",
|
|
441
|
-
icon: "🔍",
|
|
442
|
-
},
|
|
443
|
-
{
|
|
444
|
-
id: "2",
|
|
445
|
-
title: `Another Result for "${query}"`,
|
|
446
|
-
description: `Another mock result for the query "${query}"`,
|
|
447
|
-
caption: "API Result",
|
|
448
|
-
footnote: "Mock",
|
|
449
|
-
icon: "📄",
|
|
450
|
-
},
|
|
451
|
-
];
|
|
452
|
-
|
|
453
|
-
searchItems.value = mockResults;
|
|
454
|
-
} catch (err) {
|
|
455
|
-
error.value = "Failed to search. Please try again.";
|
|
456
|
-
searchItems.value = [];
|
|
457
|
-
} finally {
|
|
458
|
-
isLoading.value = false;
|
|
459
|
-
}
|
|
460
|
-
};
|
|
461
|
-
|
|
462
|
-
// Debounced search
|
|
463
|
-
let searchTimeout: ReturnType<typeof setTimeout>;
|
|
464
|
-
watch(searchQuery, (newQuery) => {
|
|
465
|
-
clearTimeout(searchTimeout);
|
|
466
|
-
searchTimeout = setTimeout(() => {
|
|
467
|
-
searchAPI(newQuery);
|
|
468
|
-
}, 300);
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
const handleSearchSelect = (result: SearchResult) => {
|
|
472
|
-
console.log("Selected from API:", result);
|
|
473
|
-
searchQuery.value = result.title;
|
|
474
|
-
};
|
|
475
|
-
</script>
|
|
476
|
-
|
|
477
|
-
<template>
|
|
478
|
-
<div class="api-search">
|
|
479
|
-
<h3>API-Powered Search</h3>
|
|
480
|
-
|
|
481
|
-
<SearchBar
|
|
482
|
-
v-model:query="searchQuery"
|
|
483
|
-
:items="searchItems"
|
|
484
|
-
type="api-results"
|
|
485
|
-
placeholder="Search with API..."
|
|
486
|
-
@onSelect="handleSearchSelect"
|
|
487
|
-
/>
|
|
488
|
-
|
|
489
|
-
<div v-if="isLoading" class="loading-state">
|
|
490
|
-
<p>Searching...</p>
|
|
491
|
-
</div>
|
|
492
|
-
|
|
493
|
-
<div v-if="error" class="error-state">
|
|
494
|
-
<p>{{ error }}</p>
|
|
495
|
-
</div>
|
|
496
|
-
|
|
497
|
-
<div v-if="searchQuery && !isLoading && !error" class="search-info">
|
|
498
|
-
<p>Found {{ searchItems.length }} results for "{{ searchQuery }}"</p>
|
|
499
|
-
</div>
|
|
500
|
-
</div>
|
|
501
|
-
</template>
|
|
502
|
-
|
|
503
|
-
<style module>
|
|
504
|
-
.api-search {
|
|
505
|
-
max-width: 500px;
|
|
506
|
-
padding: 2rem;
|
|
507
|
-
border: 1px solid #e0e0e0;
|
|
508
|
-
border-radius: 0.5rem;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
.loading-state {
|
|
512
|
-
margin-top: 1rem;
|
|
513
|
-
padding: 1rem;
|
|
514
|
-
background-color: #e3f2fd;
|
|
515
|
-
border-radius: 0.25rem;
|
|
516
|
-
text-align: center;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
.loading-state p {
|
|
520
|
-
margin: 0;
|
|
521
|
-
color: #1976d2;
|
|
522
|
-
font-weight: 500;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
.error-state {
|
|
526
|
-
margin-top: 1rem;
|
|
527
|
-
padding: 1rem;
|
|
528
|
-
background-color: #ffebee;
|
|
529
|
-
border-radius: 0.25rem;
|
|
530
|
-
text-align: center;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
.error-state p {
|
|
534
|
-
margin: 0;
|
|
535
|
-
color: #d32f2f;
|
|
536
|
-
font-weight: 500;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
.search-info {
|
|
540
|
-
margin-top: 1rem;
|
|
541
|
-
padding: 1rem;
|
|
542
|
-
background-color: #f8f9fa;
|
|
543
|
-
border-radius: 0.25rem;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
.search-info p {
|
|
547
|
-
margin: 0;
|
|
548
|
-
color: #666;
|
|
549
|
-
font-size: 0.875rem;
|
|
550
|
-
}
|
|
551
|
-
</style>
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
### Search with Custom Styling
|
|
555
|
-
|
|
556
|
-
```vue
|
|
557
|
-
<script setup lang="ts">
|
|
558
|
-
import { ref } from "vue";
|
|
559
|
-
import { SearchBar } from "@umbra-ui/core";
|
|
560
|
-
import type { SearchResult } from "@umbra-ui/core";
|
|
561
|
-
|
|
562
|
-
const searchQuery = ref("");
|
|
563
|
-
const searchItems = ref<SearchResult[]>([
|
|
564
|
-
{
|
|
565
|
-
id: "1",
|
|
566
|
-
title: "Custom Styled Search",
|
|
567
|
-
description: "This search has custom styling applied",
|
|
568
|
-
caption: "Custom",
|
|
569
|
-
footnote: "Styled",
|
|
570
|
-
icon: "🎨",
|
|
571
|
-
},
|
|
572
|
-
{
|
|
573
|
-
id: "2",
|
|
574
|
-
title: "Themed Results",
|
|
575
|
-
description: "Results with custom theme colors",
|
|
576
|
-
caption: "Theme",
|
|
577
|
-
footnote: "Custom",
|
|
578
|
-
icon: "🌈",
|
|
579
|
-
},
|
|
580
|
-
]);
|
|
581
|
-
|
|
582
|
-
const handleSearchSelect = (result: SearchResult) => {
|
|
583
|
-
console.log("Selected:", result);
|
|
584
|
-
searchQuery.value = result.title;
|
|
585
|
-
};
|
|
586
|
-
</script>
|
|
587
|
-
|
|
588
|
-
<template>
|
|
589
|
-
<div class="custom-search">
|
|
590
|
-
<h3>Custom Styled Search</h3>
|
|
591
|
-
|
|
592
|
-
<SearchBar
|
|
593
|
-
v-model:query="searchQuery"
|
|
594
|
-
:items="searchItems"
|
|
595
|
-
type="custom-items"
|
|
596
|
-
placeholder="Search with custom styling..."
|
|
597
|
-
@onSelect="handleSearchSelect"
|
|
598
|
-
/>
|
|
599
|
-
</div>
|
|
600
|
-
</template>
|
|
601
|
-
|
|
602
|
-
<style module>
|
|
603
|
-
.custom-search {
|
|
604
|
-
max-width: 500px;
|
|
605
|
-
padding: 2rem;
|
|
606
|
-
border: 1px solid #e0e0e0;
|
|
607
|
-
border-radius: 0.5rem;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
/* Custom CSS variables for theming */
|
|
611
|
-
.custom-search {
|
|
612
|
-
--search-bar-bg: #f8f9fa;
|
|
613
|
-
--search-bar-text: #495057;
|
|
614
|
-
--search-bar-placeholder: #6c757d;
|
|
615
|
-
--search-bar-border: 2px solid #dee2e6;
|
|
616
|
-
--search-bar-focus-border: #007bff;
|
|
617
|
-
|
|
618
|
-
--search-results-bg: #ffffff;
|
|
619
|
-
--search-results-border: 1px solid #dee2e6;
|
|
620
|
-
--search-results-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
|
621
|
-
|
|
622
|
-
--search-subheader-bg: #e9ecef;
|
|
623
|
-
--search-subheader-text: #495057;
|
|
624
|
-
|
|
625
|
-
--search-result-bg: #ffffff;
|
|
626
|
-
--search-result-hover-bg: #f8f9fa;
|
|
627
|
-
--search-result-text: #212529;
|
|
628
|
-
|
|
629
|
-
--search-icon-bg: #e9ecef;
|
|
630
|
-
--search-icon-border: #dee2e6;
|
|
631
|
-
|
|
632
|
-
--search-highlight-bg: rgba(255, 193, 7, 0.3);
|
|
633
|
-
--search-overlay-bg: rgba(0, 0, 0, 0.5);
|
|
634
|
-
}
|
|
635
|
-
</style>
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
### Search with Keyboard Shortcuts
|
|
639
|
-
|
|
640
|
-
```vue
|
|
641
|
-
<script setup lang="ts">
|
|
642
|
-
import { ref, onMounted, onUnmounted } from "vue";
|
|
643
|
-
import { SearchBar } from "@umbra-ui/core";
|
|
644
|
-
import type { SearchResult } from "@umbra-ui/core";
|
|
645
|
-
|
|
646
|
-
const searchQuery = ref("");
|
|
647
|
-
const searchItems = ref<SearchResult[]>([
|
|
648
|
-
{
|
|
649
|
-
id: "1",
|
|
650
|
-
title: "Keyboard Shortcuts",
|
|
651
|
-
description: "Learn about keyboard shortcuts in our application",
|
|
652
|
-
caption: "Help",
|
|
653
|
-
footnote: "Shortcuts",
|
|
654
|
-
icon: "⌨️",
|
|
655
|
-
},
|
|
656
|
-
{
|
|
657
|
-
id: "2",
|
|
658
|
-
title: "Search Tips",
|
|
659
|
-
description: "Tips and tricks for better search results",
|
|
660
|
-
caption: "Help",
|
|
661
|
-
footnote: "Tips",
|
|
662
|
-
icon: "💡",
|
|
663
|
-
},
|
|
664
|
-
]);
|
|
665
|
-
|
|
666
|
-
const searchBarRef = ref();
|
|
667
|
-
|
|
668
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
669
|
-
// Ctrl/Cmd + K to focus search
|
|
670
|
-
if ((event.ctrlKey || event.metaKey) && event.key === "k") {
|
|
671
|
-
event.preventDefault();
|
|
672
|
-
searchBarRef.value?.focus();
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
// Escape to clear search
|
|
676
|
-
if (event.key === "Escape" && searchQuery.value) {
|
|
677
|
-
searchQuery.value = "";
|
|
678
|
-
}
|
|
679
|
-
};
|
|
680
|
-
|
|
681
|
-
onMounted(() => {
|
|
682
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
onUnmounted(() => {
|
|
686
|
-
document.removeEventListener("keydown", handleKeyDown);
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
const handleSearchSelect = (result: SearchResult) => {
|
|
690
|
-
console.log("Selected:", result);
|
|
691
|
-
searchQuery.value = result.title;
|
|
692
|
-
};
|
|
693
|
-
</script>
|
|
694
|
-
|
|
695
|
-
<template>
|
|
696
|
-
<div class="keyboard-search">
|
|
697
|
-
<h3>Search with Keyboard Shortcuts</h3>
|
|
698
|
-
|
|
699
|
-
<div class="shortcuts-info">
|
|
700
|
-
<p><kbd>Ctrl</kbd> + <kbd>K</kbd> to focus search</p>
|
|
701
|
-
<p><kbd>Escape</kbd> to clear search</p>
|
|
702
|
-
</div>
|
|
703
|
-
|
|
704
|
-
<SearchBar
|
|
705
|
-
ref="searchBarRef"
|
|
706
|
-
v-model:query="searchQuery"
|
|
707
|
-
:items="searchItems"
|
|
708
|
-
type="help-items"
|
|
709
|
-
placeholder="Search help articles..."
|
|
710
|
-
@onSelect="handleSearchSelect"
|
|
711
|
-
/>
|
|
712
|
-
</div>
|
|
713
|
-
</template>
|
|
714
|
-
|
|
715
|
-
<style module>
|
|
716
|
-
.keyboard-search {
|
|
717
|
-
max-width: 500px;
|
|
718
|
-
padding: 2rem;
|
|
719
|
-
border: 1px solid #e0e0e0;
|
|
720
|
-
border-radius: 0.5rem;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
.shortcuts-info {
|
|
724
|
-
margin-bottom: 1rem;
|
|
725
|
-
padding: 1rem;
|
|
726
|
-
background-color: #f8f9fa;
|
|
727
|
-
border-radius: 0.25rem;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
.shortcuts-info p {
|
|
731
|
-
margin: 0.25rem 0;
|
|
732
|
-
color: #666;
|
|
733
|
-
font-size: 0.875rem;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
kbd {
|
|
737
|
-
background-color: #f8f9fa;
|
|
738
|
-
border: 1px solid #dee2e6;
|
|
739
|
-
border-radius: 0.25rem;
|
|
740
|
-
padding: 0.125rem 0.25rem;
|
|
741
|
-
font-size: 0.75rem;
|
|
742
|
-
font-family: monospace;
|
|
743
|
-
}
|
|
744
|
-
</style>
|
|
745
82
|
```
|
|
746
83
|
|
|
747
|
-
##
|
|
84
|
+
## Notes
|
|
748
85
|
|
|
749
|
-
-
|
|
750
|
-
-
|
|
751
|
-
- GSAP animations provide smooth transitions for showing/hiding results
|
|
752
|
-
- Search results support pagination with "Load More" functionality
|
|
753
|
-
- Query highlighting uses regex to highlight matching terms in results
|
|
754
|
-
- Keyboard navigation includes escape to close and full arrow key support
|
|
755
|
-
- Click outside functionality closes the search results
|
|
756
|
-
- Responsive design adapts to different screen sizes
|
|
757
|
-
- CSS custom properties enable easy theming for light and dark modes
|
|
758
|
-
- TypeScript interfaces ensure type safety for search results
|
|
759
|
-
- Accessibility features include ARIA labels and screen reader support
|
|
86
|
+
- The overlay scrolls to newly loaded results after `load-more`.
|
|
87
|
+
- Path segments can be wired to navigation using `segment-click` on `SearchResultCell`.
|