@farming-labs/nuxt-theme 0.2.38 → 0.2.40
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/package.json +2 -2
- package/src/components/SearchDialog.vue +267 -90
- package/styles/command-grid-bundle.css +1 -0
- package/styles/command-grid.css +1 -0
- package/styles/omni.css +263 -101
- package/styles/pixel-border.css +50 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farming-labs/nuxt-theme",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.40",
|
|
4
4
|
"description": "Nuxt/Vue UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
},
|
|
89
89
|
"dependencies": {
|
|
90
90
|
"sugar-high": "^0.9.5",
|
|
91
|
-
"@farming-labs/docs": "0.2.
|
|
91
|
+
"@farming-labs/docs": "0.2.40"
|
|
92
92
|
},
|
|
93
93
|
"peerDependencies": {
|
|
94
94
|
"nuxt": ">=3.0.0",
|
|
@@ -3,23 +3,41 @@
|
|
|
3
3
|
* Omni command palette — same behavior as website/components/ui/omni-command-palette.tsx
|
|
4
4
|
* and Astro SearchDialog: recents when empty, /api/docs search, keyboard nav, click to navigate.
|
|
5
5
|
*/
|
|
6
|
-
import { ref, computed, watch, onMounted, nextTick } from "vue";
|
|
6
|
+
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from "vue";
|
|
7
7
|
import { navigateTo } from "#app";
|
|
8
8
|
import { useRoute } from "vue-router";
|
|
9
9
|
|
|
10
10
|
const STORAGE_KEY = "fd:omni:recents";
|
|
11
11
|
const MAX_RECENTS = 8;
|
|
12
12
|
const DEBOUNCE_MS = 150;
|
|
13
|
+
const BREADCRUMB_SEPARATOR = "\u00a0\u00a0>\u00a0\u00a0";
|
|
14
|
+
const FILTER_LABELS = {
|
|
15
|
+
all: "All",
|
|
16
|
+
pages: "Pages",
|
|
17
|
+
inside: "Inside pages",
|
|
18
|
+
} as const;
|
|
19
|
+
const FILTER_OPTIONS = ["all", "pages", "inside"] as const;
|
|
13
20
|
|
|
14
21
|
const emit = defineEmits<{ (e: "close"): void }>();
|
|
15
22
|
const route = useRoute();
|
|
16
23
|
|
|
24
|
+
type SearchFilter = keyof typeof FILTER_LABELS;
|
|
25
|
+
|
|
17
26
|
const query = ref("");
|
|
18
|
-
const currentResults = ref<
|
|
27
|
+
const currentResults = ref<
|
|
28
|
+
{ content: string; url: string; description?: string; section?: string; type?: string }[]
|
|
29
|
+
>([]);
|
|
19
30
|
const loading = ref(false);
|
|
20
31
|
const activeIndex = ref(0);
|
|
32
|
+
const filter = ref<SearchFilter>("all");
|
|
33
|
+
const filterOpen = ref(false);
|
|
21
34
|
const inputEl = ref<HTMLInputElement | null>(null);
|
|
22
35
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
36
|
+
let abortController: AbortController | null = null;
|
|
37
|
+
const searchCache = new Map<
|
|
38
|
+
string,
|
|
39
|
+
{ content: string; url: string; description?: string; section?: string; type?: string }[]
|
|
40
|
+
>();
|
|
23
41
|
|
|
24
42
|
interface RecentEntry {
|
|
25
43
|
id: string;
|
|
@@ -60,20 +78,174 @@ function withLang(url: string): string {
|
|
|
60
78
|
}
|
|
61
79
|
}
|
|
62
80
|
|
|
81
|
+
function breadcrumbForUrl(url: string): string {
|
|
82
|
+
try {
|
|
83
|
+
const parsed = new URL(url, "https://farming-labs.local");
|
|
84
|
+
const parts = parsed.pathname
|
|
85
|
+
.split("/")
|
|
86
|
+
.filter(Boolean)
|
|
87
|
+
.map((part) =>
|
|
88
|
+
decodeURIComponent(part)
|
|
89
|
+
.replace(/[-_]+/g, " ")
|
|
90
|
+
.replace(/\b\w/g, (char) => char.toUpperCase()),
|
|
91
|
+
);
|
|
92
|
+
return parts.length ? parts.join(BREADCRUMB_SEPARATOR) : "Docs";
|
|
93
|
+
} catch {
|
|
94
|
+
return "Docs";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function displayLabelForResult(result: { content: string; section?: string; type?: string }): string {
|
|
99
|
+
if (result.section) return result.section;
|
|
100
|
+
const parts = result.content.split(/\s+[—–]\s+/).map((part) => part.trim()).filter(Boolean);
|
|
101
|
+
return result.type === "heading" && parts.length > 1 ? (parts[parts.length - 1] ?? result.content) : result.content;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function normalizeSearchPhrase(value?: string): string {
|
|
105
|
+
return (value ?? "").toLowerCase().replace(/[?!.,;:]+$/g, "").replace(/\s+/g, " ").trim();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function escapeRegExp(value: string): string {
|
|
109
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function literalMatchPriority(searchQuery: string, value?: string): number {
|
|
113
|
+
const q = normalizeSearchPhrase(searchQuery);
|
|
114
|
+
const text = normalizeSearchPhrase(value);
|
|
115
|
+
if (!q || !text) return 0;
|
|
116
|
+
if (text === q) return 2;
|
|
117
|
+
|
|
118
|
+
const boundary = "[^\\p{L}\\p{N}]";
|
|
119
|
+
return new RegExp(`(^|${boundary})${escapeRegExp(q)}(?=$|${boundary})`, "u").test(text)
|
|
120
|
+
? 1
|
|
121
|
+
: 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function tokenizeLiteralQuery(searchQuery: string): string[] {
|
|
125
|
+
return Array.from(
|
|
126
|
+
new Set(
|
|
127
|
+
searchQuery
|
|
128
|
+
.toLowerCase()
|
|
129
|
+
.replace(/[^\p{L}\p{N}@/_:.-]+/gu, " ")
|
|
130
|
+
.split(/\s+/)
|
|
131
|
+
.map((word) => word.replace(/^[^\p{L}\p{N}@]+|[^\p{L}\p{N}]+$/gu, ""))
|
|
132
|
+
.filter((word) => word.length > 1),
|
|
133
|
+
),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isLiteralLookupQuery(searchQuery: string): boolean {
|
|
138
|
+
const q = normalizeSearchPhrase(searchQuery);
|
|
139
|
+
const words = tokenizeLiteralQuery(q);
|
|
140
|
+
return words.length > 0 && words.length <= 3 && words.join(" ") === q;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function hasDistinctSearchSection(result: {
|
|
144
|
+
content: string;
|
|
145
|
+
section?: string;
|
|
146
|
+
type?: string;
|
|
147
|
+
}): boolean {
|
|
148
|
+
if (result.type === "page") return false;
|
|
149
|
+
if (!result.section) return true;
|
|
150
|
+
const title = (result.content ?? "").split(/\s+[—–]\s+/)[0] ?? "";
|
|
151
|
+
return normalizeSearchPhrase(result.section) !== normalizeSearchPhrase(title);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function insideLiteralPriority(
|
|
155
|
+
result: { content: string; description?: string; section?: string; type?: string },
|
|
156
|
+
searchQuery: string,
|
|
157
|
+
): number {
|
|
158
|
+
if (!hasDistinctSearchSection(result) || !isLiteralLookupQuery(searchQuery)) return 0;
|
|
159
|
+
return Math.max(
|
|
160
|
+
literalMatchPriority(searchQuery, result.section),
|
|
161
|
+
literalMatchPriority(searchQuery, result.description),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function sortResultsForFilter(
|
|
166
|
+
results: { content: string; url: string; description?: string; section?: string; type?: string; score?: number }[],
|
|
167
|
+
) {
|
|
168
|
+
const q = query.value.trim();
|
|
169
|
+
if (!q) return results;
|
|
170
|
+
return results
|
|
171
|
+
.map((result, index) => ({ result, index }))
|
|
172
|
+
.sort((a, b) => {
|
|
173
|
+
const aLiteralPriority = insideLiteralPriority(a.result, q);
|
|
174
|
+
const bLiteralPriority = insideLiteralPriority(b.result, q);
|
|
175
|
+
const literalDelta = bLiteralPriority - aLiteralPriority;
|
|
176
|
+
if (literalDelta) return literalDelta;
|
|
177
|
+
if (aLiteralPriority > 0 && bLiteralPriority > 0) return a.index - b.index;
|
|
178
|
+
|
|
179
|
+
const scoreDelta = (b.result.score ?? 0) - (a.result.score ?? 0);
|
|
180
|
+
if (scoreDelta) return scoreDelta;
|
|
181
|
+
|
|
182
|
+
return a.index - b.index;
|
|
183
|
+
})
|
|
184
|
+
.map(({ result }) => result);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function escapeHtml(value: string): string {
|
|
188
|
+
return value.replace(/[&<>"']/g, (char) => {
|
|
189
|
+
if (char === "&") return "&";
|
|
190
|
+
if (char === "<") return "<";
|
|
191
|
+
if (char === ">") return ">";
|
|
192
|
+
if (char === '"') return """;
|
|
193
|
+
return "'";
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function highlightSnippet(text: string): string {
|
|
198
|
+
const q = query.value.trim();
|
|
199
|
+
if (!q) return escapeHtml(text);
|
|
200
|
+
let html = "";
|
|
201
|
+
let lastIndex = 0;
|
|
202
|
+
const regex = new RegExp(escapeRegExp(q), "gi");
|
|
203
|
+
let match: RegExpExecArray | null;
|
|
204
|
+
|
|
205
|
+
while ((match = regex.exec(text)) !== null) {
|
|
206
|
+
html += escapeHtml(text.slice(lastIndex, match.index));
|
|
207
|
+
html += `<mark class="omni-highlight">${escapeHtml(match[0])}</mark>`;
|
|
208
|
+
lastIndex = match.index + match[0].length;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return html + escapeHtml(text.slice(lastIndex));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const visibleResults = computed(() => {
|
|
215
|
+
if (filter.value === "pages") return currentResults.value.filter((result) => result.type === "page");
|
|
216
|
+
if (filter.value === "inside") {
|
|
217
|
+
return sortResultsForFilter(currentResults.value.filter((result) => result.type !== "page"));
|
|
218
|
+
}
|
|
219
|
+
return sortResultsForFilter(currentResults.value);
|
|
220
|
+
});
|
|
221
|
+
|
|
63
222
|
const allItems = computed(() => {
|
|
64
223
|
const q = query.value.trim();
|
|
65
|
-
if (q &&
|
|
224
|
+
if (q && visibleResults.value.length) {
|
|
225
|
+
return visibleResults.value.map((r) => ({
|
|
226
|
+
id: r.url,
|
|
227
|
+
label: displayLabelForResult(r),
|
|
228
|
+
url: r.url,
|
|
229
|
+
subtitle: breadcrumbForUrl(r.url),
|
|
230
|
+
description: r.description,
|
|
231
|
+
descriptionHtml: r.description ? highlightSnippet(r.description) : "",
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
66
234
|
return recentsList.value.map((r) => ({ id: r.id, label: r.label, url: r.url, subtitle: "Recently viewed" }));
|
|
67
235
|
});
|
|
68
236
|
|
|
69
237
|
const showRecents = computed(() => !query.value.trim());
|
|
70
|
-
const showDocs = computed(() => !!query.value.trim() &&
|
|
238
|
+
const showDocs = computed(() => !!query.value.trim() && visibleResults.value.length > 0);
|
|
71
239
|
const showEmpty = computed(() => {
|
|
72
|
-
if (query.value.trim()) return
|
|
240
|
+
if (query.value.trim()) return visibleResults.value.length === 0 && !loading.value;
|
|
73
241
|
return recentsList.value.length === 0;
|
|
74
242
|
});
|
|
75
243
|
const emptyText = computed(() =>
|
|
76
|
-
query.value.trim()
|
|
244
|
+
query.value.trim()
|
|
245
|
+
? currentResults.value.length > 0
|
|
246
|
+
? `No ${FILTER_LABELS[filter.value].toLowerCase()} results found.`
|
|
247
|
+
: "No results found. Try a different query."
|
|
248
|
+
: "Type to search the docs, or browse recent items.",
|
|
77
249
|
);
|
|
78
250
|
|
|
79
251
|
function loadRecents() {
|
|
@@ -85,6 +257,12 @@ function close() {
|
|
|
85
257
|
emit("close");
|
|
86
258
|
}
|
|
87
259
|
|
|
260
|
+
function updateFilter(nextFilter: SearchFilter) {
|
|
261
|
+
filter.value = nextFilter;
|
|
262
|
+
filterOpen.value = false;
|
|
263
|
+
activeIndex.value = 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
88
266
|
function executeItem(item: { url: string; label?: string; content?: string }) {
|
|
89
267
|
const label = item.label ?? item.content ?? item.url;
|
|
90
268
|
const localizedUrl = withLang(item.url);
|
|
@@ -130,24 +308,51 @@ function onInput() {
|
|
|
130
308
|
loading.value = false;
|
|
131
309
|
currentResults.value = [];
|
|
132
310
|
activeIndex.value = 0;
|
|
311
|
+
filterOpen.value = false;
|
|
312
|
+
abortController?.abort();
|
|
133
313
|
if (!q) return;
|
|
134
314
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
315
|
+
const requestUrl = withLang(`/api/docs?query=${encodeURIComponent(q)}`);
|
|
316
|
+
const cached = searchCache.get(requestUrl);
|
|
317
|
+
if (cached) {
|
|
318
|
+
currentResults.value = cached;
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
135
321
|
debounceTimer = setTimeout(async () => {
|
|
322
|
+
const controller = new AbortController();
|
|
323
|
+
abortController = controller;
|
|
136
324
|
loading.value = true;
|
|
137
325
|
try {
|
|
138
|
-
const res = await fetch(
|
|
326
|
+
const res = await fetch(requestUrl, { signal: controller.signal });
|
|
139
327
|
const data = res.ok ? await res.json() : [];
|
|
140
|
-
|
|
328
|
+
if (controller.signal.aborted) return;
|
|
329
|
+
const nextResults = Array.isArray(data) ? data : [];
|
|
330
|
+
if (searchCache.size >= 20) {
|
|
331
|
+
const firstKey = searchCache.keys().next().value;
|
|
332
|
+
if (firstKey) searchCache.delete(firstKey);
|
|
333
|
+
}
|
|
334
|
+
searchCache.set(requestUrl, nextResults);
|
|
335
|
+
currentResults.value = nextResults;
|
|
141
336
|
activeIndex.value = 0;
|
|
142
337
|
} catch {
|
|
338
|
+
if (controller.signal.aborted) return;
|
|
143
339
|
currentResults.value = [];
|
|
144
340
|
} finally {
|
|
145
|
-
|
|
341
|
+
if (abortController === controller) {
|
|
342
|
+
abortController = null;
|
|
343
|
+
loading.value = false;
|
|
344
|
+
}
|
|
146
345
|
}
|
|
147
346
|
}, DEBOUNCE_MS);
|
|
148
347
|
}
|
|
149
348
|
|
|
150
349
|
watch(query, onInput);
|
|
350
|
+
watch(filter, () => {
|
|
351
|
+
activeIndex.value = 0;
|
|
352
|
+
});
|
|
353
|
+
watch(visibleResults, () => {
|
|
354
|
+
if (activeIndex.value >= visibleResults.value.length) activeIndex.value = 0;
|
|
355
|
+
});
|
|
151
356
|
|
|
152
357
|
function handleKeydown(e: KeyboardEvent) {
|
|
153
358
|
if (e.key === "Escape") {
|
|
@@ -183,16 +388,6 @@ function onRowClick(item: { url: string; label?: string; content?: string }) {
|
|
|
183
388
|
executeItem(item);
|
|
184
389
|
}
|
|
185
390
|
|
|
186
|
-
function onExternalClick(e: Event, url: string) {
|
|
187
|
-
e.preventDefault();
|
|
188
|
-
e.stopPropagation();
|
|
189
|
-
try {
|
|
190
|
-
window.open(withLang(url), "_blank", "noopener,noreferrer");
|
|
191
|
-
} catch {
|
|
192
|
-
window.location.href = withLang(url);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
391
|
function onRowMouseEnter(container: "recent" | "docs", index: number) {
|
|
197
392
|
activeIndex.value = index;
|
|
198
393
|
}
|
|
@@ -204,6 +399,12 @@ onMounted(() => {
|
|
|
204
399
|
inputEl.value?.focus();
|
|
205
400
|
});
|
|
206
401
|
});
|
|
402
|
+
|
|
403
|
+
onBeforeUnmount(() => {
|
|
404
|
+
if (typeof document !== "undefined") document.body.style.overflow = "";
|
|
405
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
406
|
+
abortController?.abort();
|
|
407
|
+
});
|
|
207
408
|
</script>
|
|
208
409
|
|
|
209
410
|
<template>
|
|
@@ -230,16 +431,12 @@ onMounted(() => {
|
|
|
230
431
|
role="combobox"
|
|
231
432
|
aria-expanded="true"
|
|
232
433
|
aria-controls="fd-omni-listbox"
|
|
233
|
-
placeholder="Search
|
|
434
|
+
placeholder="Search"
|
|
234
435
|
autocomplete="off"
|
|
235
436
|
@keydown="handleKeydown"
|
|
236
437
|
/>
|
|
237
|
-
<kbd class="omni-kbd">⌘ K</kbd>
|
|
238
438
|
<button type="button" aria-label="Close" class="omni-close-btn" @click="close">
|
|
239
|
-
|
|
240
|
-
<path d="M18 6 6 18" />
|
|
241
|
-
<path d="m6 6 12 12" />
|
|
242
|
-
</svg>
|
|
439
|
+
ESC
|
|
243
440
|
</button>
|
|
244
441
|
</div>
|
|
245
442
|
</div>
|
|
@@ -267,35 +464,10 @@ onMounted(() => {
|
|
|
267
464
|
@click="onRowClick(r)"
|
|
268
465
|
@mouseenter="onRowMouseEnter('recent', i)"
|
|
269
466
|
>
|
|
270
|
-
<div class="omni-item-icon">
|
|
271
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
272
|
-
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
|
|
273
|
-
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
|
|
274
|
-
</svg>
|
|
275
|
-
</div>
|
|
276
467
|
<div class="omni-item-text">
|
|
277
|
-
<div class="omni-item-label">{{ r.label }}</div>
|
|
278
468
|
<div class="omni-item-subtitle">Recently viewed</div>
|
|
469
|
+
<div class="omni-item-label">{{ r.label }}</div>
|
|
279
470
|
</div>
|
|
280
|
-
<a
|
|
281
|
-
:href="r.url"
|
|
282
|
-
class="omni-item-ext"
|
|
283
|
-
title="Open in new tab"
|
|
284
|
-
target="_blank"
|
|
285
|
-
rel="noopener noreferrer"
|
|
286
|
-
@click.prevent="onExternalClick($event, r.url)"
|
|
287
|
-
>
|
|
288
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
289
|
-
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
290
|
-
<polyline points="15 3 21 3 21 9" />
|
|
291
|
-
<line x1="10" y1="14" x2="21" y2="3" />
|
|
292
|
-
</svg>
|
|
293
|
-
</a>
|
|
294
|
-
<span class="omni-item-chevron" aria-hidden="true">
|
|
295
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
296
|
-
<polyline points="9 18 15 12 9 6" />
|
|
297
|
-
</svg>
|
|
298
|
-
</span>
|
|
299
471
|
</div>
|
|
300
472
|
</div>
|
|
301
473
|
</div>
|
|
@@ -304,8 +476,8 @@ onMounted(() => {
|
|
|
304
476
|
<div class="omni-group-label">Documentation</div>
|
|
305
477
|
<div id="fd-omni-docs-items" class="omni-group-items">
|
|
306
478
|
<div
|
|
307
|
-
v-for="(r, i) in
|
|
308
|
-
:key="r.
|
|
479
|
+
v-for="(r, i) in allItems"
|
|
480
|
+
:key="r.id"
|
|
309
481
|
class="omni-item"
|
|
310
482
|
:class="{ 'omni-item-active': showDocs && i === activeIndex }"
|
|
311
483
|
:data-url="r.url"
|
|
@@ -315,35 +487,15 @@ onMounted(() => {
|
|
|
315
487
|
@click="onRowClick(r)"
|
|
316
488
|
@mouseenter="onRowMouseEnter('docs', i)"
|
|
317
489
|
>
|
|
318
|
-
<div class="omni-item-icon">
|
|
319
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
320
|
-
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
|
|
321
|
-
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
|
|
322
|
-
</svg>
|
|
323
|
-
</div>
|
|
324
490
|
<div class="omni-item-text">
|
|
325
|
-
<div class="omni-item-
|
|
326
|
-
<div class="omni-item-
|
|
491
|
+
<div class="omni-item-subtitle">{{ r.subtitle }}</div>
|
|
492
|
+
<div class="omni-item-label">{{ r.label }}</div>
|
|
493
|
+
<div
|
|
494
|
+
v-if="r.description"
|
|
495
|
+
class="omni-item-description"
|
|
496
|
+
v-html="r.descriptionHtml"
|
|
497
|
+
/>
|
|
327
498
|
</div>
|
|
328
|
-
<a
|
|
329
|
-
:href="r.url"
|
|
330
|
-
class="omni-item-ext"
|
|
331
|
-
title="Open in new tab"
|
|
332
|
-
target="_blank"
|
|
333
|
-
rel="noopener noreferrer"
|
|
334
|
-
@click.prevent="onExternalClick($event, r.url)"
|
|
335
|
-
>
|
|
336
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
337
|
-
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
338
|
-
<polyline points="15 3 21 3 21 9" />
|
|
339
|
-
<line x1="10" y1="14" x2="21" y2="3" />
|
|
340
|
-
</svg>
|
|
341
|
-
</a>
|
|
342
|
-
<span class="omni-item-chevron" aria-hidden="true">
|
|
343
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
344
|
-
<polyline points="9 18 15 12 9 6" />
|
|
345
|
-
</svg>
|
|
346
|
-
</span>
|
|
347
499
|
</div>
|
|
348
500
|
</div>
|
|
349
501
|
</div>
|
|
@@ -363,28 +515,53 @@ onMounted(() => {
|
|
|
363
515
|
<div class="omni-footer-inner">
|
|
364
516
|
<div class="omni-footer-hints">
|
|
365
517
|
<span class="omni-footer-hint">
|
|
366
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
367
|
-
<
|
|
518
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
519
|
+
<path d="m9 10-5 5 5 5" />
|
|
520
|
+
<path d="M20 4v7a4 4 0 0 1-4 4H4" />
|
|
368
521
|
</svg>
|
|
369
522
|
to select
|
|
370
523
|
</span>
|
|
371
524
|
<span class="omni-footer-hint">
|
|
372
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
373
|
-
<path d="
|
|
525
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
526
|
+
<path d="m18 15-6-6-6 6" />
|
|
374
527
|
</svg>
|
|
375
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
376
|
-
<path d="
|
|
528
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
529
|
+
<path d="m6 9 6 6 6-6" />
|
|
377
530
|
</svg>
|
|
378
531
|
to navigate
|
|
379
532
|
</span>
|
|
380
533
|
<span class="omni-footer-hint omni-footer-hint-desktop">
|
|
381
|
-
<
|
|
382
|
-
<path d="M18 6 6 18" />
|
|
383
|
-
<path d="m6 6 12 12" />
|
|
384
|
-
</svg>
|
|
534
|
+
<span class="omni-kbd-sm">ESC</span>
|
|
385
535
|
to close
|
|
386
536
|
</span>
|
|
387
537
|
</div>
|
|
538
|
+
<div class="omni-footer-filter">
|
|
539
|
+
<span class="omni-filter-label">Filter</span>
|
|
540
|
+
<button
|
|
541
|
+
type="button"
|
|
542
|
+
class="omni-filter-button"
|
|
543
|
+
:aria-expanded="filterOpen"
|
|
544
|
+
@click="filterOpen = !filterOpen"
|
|
545
|
+
>
|
|
546
|
+
{{ FILTER_LABELS[filter] }}
|
|
547
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
548
|
+
<path d="M6 9l6 6 6-6" />
|
|
549
|
+
</svg>
|
|
550
|
+
</button>
|
|
551
|
+
<div v-if="filterOpen" class="omni-filter-menu" role="group" aria-label="Search filter">
|
|
552
|
+
<button
|
|
553
|
+
v-for="option in FILTER_OPTIONS"
|
|
554
|
+
:key="option"
|
|
555
|
+
type="button"
|
|
556
|
+
:aria-pressed="filter === option"
|
|
557
|
+
class="omni-filter-option"
|
|
558
|
+
:class="{ 'omni-filter-option-active': filter === option }"
|
|
559
|
+
@click="updateFilter(option)"
|
|
560
|
+
>
|
|
561
|
+
{{ FILTER_LABELS[option] }}
|
|
562
|
+
</button>
|
|
563
|
+
</div>
|
|
564
|
+
</div>
|
|
388
565
|
</div>
|
|
389
566
|
</div>
|
|
390
567
|
</div>
|
package/styles/command-grid.css
CHANGED
package/styles/omni.css
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
/* ═══════════════════════════════════════════════════════════════════
|
|
2
|
-
* Omni Command Palette — core styles
|
|
3
|
-
* Uses
|
|
2
|
+
* Omni Command Palette — core styles
|
|
3
|
+
* Uses fumadocs CSS variables so it auto-adapts to every theme.
|
|
4
4
|
* ═══════════════════════════════════════════════════════════════════ */
|
|
5
5
|
|
|
6
6
|
@keyframes omni-fade-in {
|
|
7
|
-
from {
|
|
8
|
-
|
|
7
|
+
from {
|
|
8
|
+
opacity: 0;
|
|
9
|
+
}
|
|
10
|
+
to {
|
|
11
|
+
opacity: 1;
|
|
12
|
+
}
|
|
9
13
|
}
|
|
10
14
|
@keyframes omni-fade-out {
|
|
11
|
-
from {
|
|
12
|
-
|
|
15
|
+
from {
|
|
16
|
+
opacity: 1;
|
|
17
|
+
}
|
|
18
|
+
to {
|
|
19
|
+
opacity: 0;
|
|
20
|
+
}
|
|
13
21
|
}
|
|
14
22
|
@keyframes omni-scale-in {
|
|
15
23
|
from {
|
|
16
24
|
opacity: 0;
|
|
17
|
-
transform: translateX(-50%) scale(0.
|
|
25
|
+
transform: translateX(-50%) scale(0.98) translateY(-6px);
|
|
18
26
|
}
|
|
19
27
|
to {
|
|
20
28
|
opacity: 1;
|
|
@@ -28,13 +36,19 @@
|
|
|
28
36
|
}
|
|
29
37
|
to {
|
|
30
38
|
opacity: 0;
|
|
31
|
-
transform: translateX(-50%) scale(0.
|
|
39
|
+
transform: translateX(-50%) scale(0.98) translateY(-6px);
|
|
32
40
|
}
|
|
33
41
|
}
|
|
42
|
+
|
|
34
43
|
@keyframes omni-spin {
|
|
35
|
-
from {
|
|
36
|
-
|
|
44
|
+
from {
|
|
45
|
+
transform: rotate(0deg);
|
|
46
|
+
}
|
|
47
|
+
to {
|
|
48
|
+
transform: rotate(360deg);
|
|
49
|
+
}
|
|
37
50
|
}
|
|
51
|
+
|
|
38
52
|
.omni-spin {
|
|
39
53
|
animation: omni-spin 1s linear infinite;
|
|
40
54
|
}
|
|
@@ -42,38 +56,44 @@
|
|
|
42
56
|
.omni-overlay {
|
|
43
57
|
position: fixed;
|
|
44
58
|
inset: 0;
|
|
45
|
-
z-index:
|
|
46
|
-
background: rgba(0, 0, 0, 0.
|
|
47
|
-
backdrop-filter: blur(
|
|
59
|
+
z-index: 50;
|
|
60
|
+
background: rgba(0, 0, 0, 0.2);
|
|
61
|
+
backdrop-filter: blur(2px);
|
|
48
62
|
animation: omni-fade-in 150ms ease-out;
|
|
49
63
|
}
|
|
64
|
+
|
|
50
65
|
.omni-content {
|
|
66
|
+
--omni-content-top: clamp(5rem, 16vh, 7rem);
|
|
51
67
|
position: fixed;
|
|
52
|
-
z-index:
|
|
53
|
-
top:
|
|
68
|
+
z-index: 51;
|
|
69
|
+
top: var(--omni-content-top);
|
|
54
70
|
left: 50%;
|
|
55
71
|
transform: translateX(-50%);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
width: min(720px, calc(100% - 1rem));
|
|
75
|
+
border-radius: 0.75rem;
|
|
76
|
+
border: 1px solid color-mix(in srgb, var(--color-fd-border, #d4d4d4) 70%, transparent);
|
|
77
|
+
background: var(--color-fd-popover, #fafafa);
|
|
78
|
+
color: var(--color-fd-popover-foreground, var(--color-fd-foreground, #272727));
|
|
79
|
+
font-family: var(--font-sans, system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text",
|
|
80
|
+
ui-sans-serif, sans-serif);
|
|
81
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
64
82
|
outline: none;
|
|
65
|
-
|
|
83
|
+
overscroll-behavior: contain;
|
|
66
84
|
animation: omni-scale-in 200ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
67
85
|
}
|
|
86
|
+
|
|
68
87
|
@media (max-width: 639px) {
|
|
69
88
|
.omni-content {
|
|
70
|
-
top:
|
|
71
|
-
|
|
72
|
-
|
|
89
|
+
--omni-content-top: 1rem;
|
|
90
|
+
top: var(--omni-content-top);
|
|
91
|
+
width: calc(100% - 1rem);
|
|
92
|
+
max-height: calc(100vh - 2rem);
|
|
73
93
|
}
|
|
74
|
-
.omni-kbd { display: none; }
|
|
75
94
|
}
|
|
76
95
|
|
|
96
|
+
/* Header */
|
|
77
97
|
.omni-header {
|
|
78
98
|
border-bottom: 1px solid var(--color-fd-border, #262626);
|
|
79
99
|
}
|
|
@@ -81,90 +101,120 @@
|
|
|
81
101
|
display: flex;
|
|
82
102
|
align-items: center;
|
|
83
103
|
gap: 0.5rem;
|
|
84
|
-
padding: 0.
|
|
104
|
+
padding: 0.75rem;
|
|
85
105
|
}
|
|
86
106
|
.omni-search-icon {
|
|
87
107
|
color: var(--color-fd-muted-foreground, #a3a3a3);
|
|
88
108
|
flex-shrink: 0;
|
|
89
109
|
}
|
|
110
|
+
.omni-search-icon svg {
|
|
111
|
+
width: 1.25rem;
|
|
112
|
+
height: 1.25rem;
|
|
113
|
+
}
|
|
90
114
|
.omni-search-input {
|
|
115
|
+
width: 0;
|
|
91
116
|
flex: 1;
|
|
92
117
|
background: transparent;
|
|
93
118
|
outline: none;
|
|
94
|
-
font-size:
|
|
95
|
-
line-height: 1.
|
|
96
|
-
color: var(--color-fd-foreground, #
|
|
119
|
+
font-size: 1.125rem;
|
|
120
|
+
line-height: 1.75rem;
|
|
121
|
+
color: var(--color-fd-popover-foreground, var(--color-fd-foreground, #272727));
|
|
97
122
|
border: none;
|
|
98
123
|
}
|
|
99
124
|
.omni-search-input::placeholder {
|
|
100
125
|
color: var(--color-fd-muted-foreground, #a3a3a3);
|
|
101
126
|
}
|
|
102
127
|
.omni-kbd {
|
|
103
|
-
border-radius: 0.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
128
|
+
border-radius: 0.375rem;
|
|
129
|
+
border: 1px solid var(--color-fd-border, #d4d4d4);
|
|
130
|
+
background: transparent;
|
|
131
|
+
padding: 0.375rem 0.5rem;
|
|
132
|
+
font-size: 0.75rem;
|
|
107
133
|
color: var(--color-fd-muted-foreground, #a3a3a3);
|
|
108
|
-
font-family:
|
|
109
|
-
|
|
134
|
+
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
|
135
|
+
"Liberation Mono", "Courier New", monospace);
|
|
136
|
+
line-height: 1rem;
|
|
110
137
|
}
|
|
111
138
|
.omni-close-btn {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
139
|
+
display: inline-flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
justify-content: center;
|
|
142
|
+
gap: 0.25rem;
|
|
143
|
+
min-width: 2.5rem;
|
|
144
|
+
border-radius: 0.375rem;
|
|
145
|
+
border: 1px solid var(--color-fd-border, #d4d4d4);
|
|
146
|
+
padding: 0.375rem 0.5rem;
|
|
115
147
|
color: var(--color-fd-muted-foreground, #a3a3a3);
|
|
116
|
-
|
|
148
|
+
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
|
149
|
+
"Liberation Mono", "Courier New", monospace);
|
|
150
|
+
font-size: 0.75rem;
|
|
151
|
+
line-height: 1rem;
|
|
152
|
+
font-weight: 500;
|
|
153
|
+
transition:
|
|
154
|
+
background 120ms,
|
|
155
|
+
color 120ms;
|
|
117
156
|
cursor: pointer;
|
|
118
|
-
border: none;
|
|
119
157
|
background: none;
|
|
120
158
|
}
|
|
121
159
|
.omni-close-btn:hover {
|
|
122
|
-
|
|
160
|
+
color: var(--color-fd-accent-foreground, var(--color-fd-foreground, #171717));
|
|
161
|
+
background: var(--color-fd-accent, #e5e5e5);
|
|
162
|
+
}
|
|
163
|
+
.omni-close-btn svg {
|
|
164
|
+
display: none;
|
|
123
165
|
}
|
|
124
166
|
|
|
167
|
+
/* Body / listbox */
|
|
125
168
|
.omni-body {
|
|
126
|
-
|
|
169
|
+
flex: 1 1 auto;
|
|
170
|
+
min-height: 0;
|
|
171
|
+
max-height: min(540px, calc(100vh - var(--omni-content-top, 7rem) - 8rem));
|
|
127
172
|
overflow: auto;
|
|
128
|
-
|
|
173
|
+
overscroll-behavior: contain;
|
|
174
|
+
padding: 0.5rem;
|
|
175
|
+
}
|
|
176
|
+
.omni-body::-webkit-scrollbar {
|
|
177
|
+
width: 6px;
|
|
178
|
+
}
|
|
179
|
+
.omni-body::-webkit-scrollbar-track {
|
|
180
|
+
background: transparent;
|
|
129
181
|
}
|
|
130
|
-
.omni-body::-webkit-scrollbar { width: 6px; }
|
|
131
|
-
.omni-body::-webkit-scrollbar-track { background: transparent; }
|
|
132
182
|
.omni-body::-webkit-scrollbar-thumb {
|
|
133
183
|
background: var(--color-fd-muted, #262626);
|
|
134
184
|
border-radius: 3px;
|
|
135
185
|
}
|
|
136
186
|
|
|
187
|
+
/* Loading */
|
|
137
188
|
.omni-loading {
|
|
138
189
|
display: flex;
|
|
139
190
|
align-items: center;
|
|
140
191
|
gap: 0.5rem;
|
|
141
|
-
padding: 0.
|
|
192
|
+
padding: 0.75rem 0.625rem;
|
|
142
193
|
color: var(--color-fd-muted-foreground, #a3a3a3);
|
|
143
|
-
font-size: 0.
|
|
194
|
+
font-size: 0.875rem;
|
|
144
195
|
}
|
|
196
|
+
|
|
197
|
+
/* Groups */
|
|
145
198
|
.omni-group {
|
|
146
|
-
padding: 0
|
|
199
|
+
padding: 0;
|
|
147
200
|
}
|
|
148
201
|
.omni-group-label {
|
|
149
|
-
|
|
150
|
-
font-size: 10px;
|
|
151
|
-
text-transform: uppercase;
|
|
152
|
-
letter-spacing: 0.06em;
|
|
153
|
-
color: var(--color-fd-muted-foreground, #a3a3a3);
|
|
154
|
-
font-weight: 500;
|
|
202
|
+
display: none;
|
|
155
203
|
}
|
|
156
204
|
.omni-group-items {
|
|
157
205
|
display: flex;
|
|
158
206
|
flex-direction: column;
|
|
207
|
+
gap: 0.25rem;
|
|
159
208
|
}
|
|
160
209
|
|
|
210
|
+
/* Items */
|
|
161
211
|
.omni-item {
|
|
162
|
-
|
|
212
|
+
position: relative;
|
|
213
|
+
display: block;
|
|
163
214
|
width: 100%;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
padding: 0.5rem 0.75rem;
|
|
215
|
+
flex-shrink: 0;
|
|
216
|
+
border-radius: 0.5rem;
|
|
217
|
+
padding: 0.75rem 0.875rem;
|
|
168
218
|
text-align: left;
|
|
169
219
|
font-size: 0.875rem;
|
|
170
220
|
line-height: 1.25rem;
|
|
@@ -176,81 +226,112 @@
|
|
|
176
226
|
}
|
|
177
227
|
.omni-item:hover,
|
|
178
228
|
.omni-item-active {
|
|
179
|
-
background:
|
|
229
|
+
background: var(--color-fd-accent, rgba(209, 209, 209, 0.5));
|
|
180
230
|
}
|
|
181
231
|
.omni-item-active {
|
|
182
|
-
color: var(--color-fd-accent-foreground, #
|
|
232
|
+
color: var(--color-fd-accent-foreground, #171717);
|
|
183
233
|
}
|
|
184
234
|
.omni-item-disabled {
|
|
185
235
|
opacity: 0.5;
|
|
186
236
|
cursor: not-allowed;
|
|
187
237
|
}
|
|
238
|
+
|
|
188
239
|
.omni-item-icon {
|
|
189
|
-
|
|
190
|
-
color: var(--color-fd-muted-foreground, #a3a3a3);
|
|
240
|
+
display: none;
|
|
191
241
|
}
|
|
192
242
|
.omni-item-active .omni-item-icon {
|
|
193
243
|
color: var(--color-fd-accent-foreground, #fafafa);
|
|
194
244
|
}
|
|
195
|
-
.omni-item-text {
|
|
245
|
+
.omni-item-text {
|
|
246
|
+
min-width: 0;
|
|
247
|
+
}
|
|
196
248
|
.omni-item-label {
|
|
249
|
+
min-width: 0;
|
|
197
250
|
overflow: hidden;
|
|
198
251
|
text-overflow: ellipsis;
|
|
199
252
|
white-space: nowrap;
|
|
253
|
+
color: var(--color-fd-popover-foreground, var(--color-fd-foreground, #272727));
|
|
254
|
+
font-weight: 500;
|
|
255
|
+
line-height: 1.35;
|
|
200
256
|
}
|
|
201
257
|
.omni-item-subtitle {
|
|
258
|
+
min-width: 0;
|
|
202
259
|
overflow: hidden;
|
|
203
260
|
text-overflow: ellipsis;
|
|
204
261
|
white-space: nowrap;
|
|
205
262
|
font-size: 0.75rem;
|
|
263
|
+
line-height: 1rem;
|
|
206
264
|
color: var(--color-fd-muted-foreground, #a3a3a3);
|
|
207
|
-
opacity:
|
|
265
|
+
opacity: 1;
|
|
208
266
|
}
|
|
209
267
|
.omni-item-active .omni-item-subtitle {
|
|
210
|
-
color: var(--color-fd-
|
|
211
|
-
opacity:
|
|
268
|
+
color: var(--color-fd-muted-foreground, #737373);
|
|
269
|
+
opacity: 1;
|
|
270
|
+
}
|
|
271
|
+
.omni-item-description {
|
|
272
|
+
min-width: 0;
|
|
273
|
+
overflow: hidden;
|
|
274
|
+
display: -webkit-box;
|
|
275
|
+
-webkit-line-clamp: 3;
|
|
276
|
+
-webkit-box-orient: vertical;
|
|
277
|
+
margin-top: 0.625rem;
|
|
278
|
+
padding-left: 0.75rem;
|
|
279
|
+
border-left: 1px solid color-mix(in srgb, var(--color-fd-border, #d4d4d4) 70%, transparent);
|
|
280
|
+
color: color-mix(in srgb, var(--color-fd-popover-foreground, #272727) 82%, transparent);
|
|
281
|
+
font-size: 0.875rem;
|
|
282
|
+
line-height: 1.5rem;
|
|
283
|
+
font-weight: 400;
|
|
212
284
|
}
|
|
213
285
|
.omni-item-badge {
|
|
214
|
-
|
|
215
|
-
flex-shrink: 0;
|
|
286
|
+
display: none;
|
|
216
287
|
}
|
|
217
288
|
.omni-item-ext {
|
|
218
|
-
|
|
219
|
-
z-index: 2;
|
|
220
|
-
display: inline-flex;
|
|
221
|
-
align-items: center;
|
|
222
|
-
justify-content: center;
|
|
223
|
-
padding: 0.25rem;
|
|
224
|
-
border-radius: 0.25rem;
|
|
225
|
-
color: var(--color-fd-muted-foreground, #a3a3a3);
|
|
226
|
-
flex-shrink: 0;
|
|
227
|
-
transition: color 100ms, background 100ms;
|
|
228
|
-
text-decoration: none;
|
|
289
|
+
display: none;
|
|
229
290
|
}
|
|
230
291
|
.omni-item-ext:hover {
|
|
231
292
|
color: var(--color-fd-foreground, #fafafa);
|
|
232
293
|
background: var(--color-fd-muted, #262626);
|
|
233
294
|
}
|
|
234
|
-
.omni-item-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
295
|
+
.omni-item-shortcuts {
|
|
296
|
+
display: none;
|
|
297
|
+
align-items: center;
|
|
298
|
+
gap: 0.25rem;
|
|
299
|
+
font-size: 10px;
|
|
238
300
|
color: var(--color-fd-muted-foreground, #a3a3a3);
|
|
239
|
-
opacity: 0;
|
|
240
|
-
transition: opacity 120ms;
|
|
241
|
-
flex-shrink: 0;
|
|
242
301
|
}
|
|
243
|
-
|
|
244
|
-
|
|
302
|
+
@media (min-width: 640px) {
|
|
303
|
+
.omni-item-shortcuts {
|
|
304
|
+
display: flex;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
.omni-kbd-sm {
|
|
308
|
+
border-radius: 0.25rem;
|
|
309
|
+
background: var(--color-fd-muted, #262626);
|
|
310
|
+
padding: 0.125rem 0.25rem;
|
|
311
|
+
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
|
312
|
+
"Liberation Mono", "Courier New", monospace);
|
|
313
|
+
}
|
|
314
|
+
.omni-item-chevron {
|
|
315
|
+
display: none;
|
|
245
316
|
}
|
|
317
|
+
/* Highlight */
|
|
246
318
|
.omni-highlight {
|
|
247
|
-
border-radius:
|
|
248
|
-
background:
|
|
249
|
-
padding: 0
|
|
250
|
-
color:
|
|
319
|
+
border-radius: 0;
|
|
320
|
+
background: transparent;
|
|
321
|
+
padding: 0;
|
|
322
|
+
color: var(--color-fd-primary, #ca8a04);
|
|
323
|
+
text-decoration: underline;
|
|
324
|
+
text-underline-offset: 2px;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* Empty states */
|
|
328
|
+
.omni-empty-group {
|
|
329
|
+
padding: 0.5rem 0.75rem;
|
|
330
|
+
font-size: 0.75rem;
|
|
331
|
+
color: var(--color-fd-muted-foreground, #a3a3a3);
|
|
251
332
|
}
|
|
252
333
|
.omni-empty {
|
|
253
|
-
padding:
|
|
334
|
+
padding: 1.5rem 0.75rem;
|
|
254
335
|
text-align: center;
|
|
255
336
|
font-size: 0.875rem;
|
|
256
337
|
color: var(--color-fd-muted-foreground, #a3a3a3);
|
|
@@ -265,28 +346,109 @@
|
|
|
265
346
|
border-radius: 9999px;
|
|
266
347
|
background: var(--color-fd-muted, #262626);
|
|
267
348
|
}
|
|
349
|
+
|
|
350
|
+
/* Footer */
|
|
268
351
|
.omni-footer {
|
|
269
352
|
border-top: 1px solid var(--color-fd-border, #262626);
|
|
353
|
+
background: color-mix(in srgb, var(--color-fd-secondary, #f5f5f5) 50%, transparent);
|
|
270
354
|
}
|
|
271
355
|
.omni-footer-inner {
|
|
272
356
|
display: flex;
|
|
273
357
|
align-items: center;
|
|
274
358
|
justify-content: space-between;
|
|
275
|
-
|
|
359
|
+
gap: 0.75rem;
|
|
360
|
+
padding: 0.625rem 0.75rem;
|
|
276
361
|
font-size: 0.75rem;
|
|
277
362
|
color: var(--color-fd-muted-foreground, #a3a3a3);
|
|
278
363
|
}
|
|
279
364
|
.omni-footer-hints {
|
|
280
365
|
display: flex;
|
|
281
366
|
align-items: center;
|
|
282
|
-
|
|
367
|
+
flex-wrap: wrap;
|
|
368
|
+
gap: 0.75rem;
|
|
283
369
|
}
|
|
284
370
|
.omni-footer-hint {
|
|
285
371
|
display: flex;
|
|
286
372
|
align-items: center;
|
|
287
373
|
gap: 0.25rem;
|
|
374
|
+
white-space: nowrap;
|
|
375
|
+
}
|
|
376
|
+
.omni-footer-hint svg {
|
|
377
|
+
box-sizing: border-box;
|
|
378
|
+
width: 1.375rem;
|
|
379
|
+
height: 1.375rem;
|
|
380
|
+
padding: 0.25rem;
|
|
381
|
+
border-radius: 0.375rem;
|
|
382
|
+
border: 1px solid var(--color-fd-border, #d4d4d4);
|
|
383
|
+
background: color-mix(in srgb, var(--color-fd-muted, #e5e5e5) 70%, transparent);
|
|
384
|
+
color: var(--color-fd-muted-foreground, #737373);
|
|
385
|
+
flex-shrink: 0;
|
|
386
|
+
}
|
|
387
|
+
.omni-footer-hint-desktop {
|
|
388
|
+
display: none;
|
|
389
|
+
}
|
|
390
|
+
.omni-footer-filter {
|
|
391
|
+
position: relative;
|
|
392
|
+
display: inline-flex;
|
|
393
|
+
align-items: center;
|
|
394
|
+
gap: 0.5rem;
|
|
395
|
+
margin-left: auto;
|
|
396
|
+
white-space: nowrap;
|
|
288
397
|
}
|
|
289
|
-
.omni-footer-hint-desktop { display: none; }
|
|
290
398
|
@media (min-width: 640px) {
|
|
291
|
-
.omni-footer-hint-desktop {
|
|
399
|
+
.omni-footer-hint-desktop {
|
|
400
|
+
display: flex;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
.omni-filter-label {
|
|
404
|
+
color: var(--color-fd-muted-foreground, #a3a3a3);
|
|
405
|
+
}
|
|
406
|
+
.omni-filter-value {
|
|
407
|
+
display: inline-flex;
|
|
408
|
+
align-items: center;
|
|
409
|
+
gap: 0.25rem;
|
|
410
|
+
color: var(--color-fd-popover-foreground, var(--color-fd-foreground, #272727));
|
|
411
|
+
}
|
|
412
|
+
.omni-filter-button {
|
|
413
|
+
display: inline-flex;
|
|
414
|
+
align-items: center;
|
|
415
|
+
gap: 0.25rem;
|
|
416
|
+
border: 0;
|
|
417
|
+
background: transparent;
|
|
418
|
+
color: var(--color-fd-popover-foreground, var(--color-fd-foreground, #272727));
|
|
419
|
+
font: inherit;
|
|
420
|
+
cursor: pointer;
|
|
421
|
+
padding: 0;
|
|
422
|
+
}
|
|
423
|
+
.omni-filter-button:hover {
|
|
424
|
+
color: var(--color-fd-accent-foreground, var(--color-fd-foreground, #171717));
|
|
425
|
+
}
|
|
426
|
+
.omni-filter-menu {
|
|
427
|
+
position: absolute;
|
|
428
|
+
right: 0;
|
|
429
|
+
bottom: calc(100% + 0.5rem);
|
|
430
|
+
z-index: 1;
|
|
431
|
+
width: 10rem;
|
|
432
|
+
border: 1px solid var(--color-fd-border, #d4d4d4);
|
|
433
|
+
border-radius: 0.5rem;
|
|
434
|
+
background: var(--color-fd-popover, #fafafa);
|
|
435
|
+
padding: 0.25rem;
|
|
436
|
+
box-shadow: 0 16px 32px -20px rgba(0, 0, 0, 0.45);
|
|
437
|
+
}
|
|
438
|
+
.omni-filter-option {
|
|
439
|
+
display: flex;
|
|
440
|
+
width: 100%;
|
|
441
|
+
align-items: center;
|
|
442
|
+
border: 0;
|
|
443
|
+
border-radius: 0.375rem;
|
|
444
|
+
background: transparent;
|
|
445
|
+
color: var(--color-fd-popover-foreground, var(--color-fd-foreground, #272727));
|
|
446
|
+
cursor: pointer;
|
|
447
|
+
font: inherit;
|
|
448
|
+
padding: 0.5rem 0.625rem;
|
|
449
|
+
text-align: left;
|
|
450
|
+
}
|
|
451
|
+
.omni-filter-option:hover,
|
|
452
|
+
.omni-filter-option-active {
|
|
453
|
+
background: var(--color-fd-accent, rgba(209, 209, 209, 0.5));
|
|
292
454
|
}
|
package/styles/pixel-border.css
CHANGED
|
@@ -841,6 +841,56 @@ code:not(pre code) {
|
|
|
841
841
|
color: var(--color-fd-foreground) !important;
|
|
842
842
|
}
|
|
843
843
|
|
|
844
|
+
/* ─── Omni Command Palette — pixel-border theme ──────────────────── */
|
|
845
|
+
|
|
846
|
+
.omni-content {
|
|
847
|
+
border-radius: 0 !important;
|
|
848
|
+
border: 2px solid var(--color-fd-border, hsl(0 0% 15%)) !important;
|
|
849
|
+
box-shadow:
|
|
850
|
+
4px 4px 0 0 var(--color-fd-border, hsl(0 0% 15%)),
|
|
851
|
+
0 24px 60px -12px rgba(0, 0, 0, 0.5) !important;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.omni-item,
|
|
855
|
+
.omni-kbd,
|
|
856
|
+
.omni-kbd-sm,
|
|
857
|
+
.omni-close-btn,
|
|
858
|
+
.omni-empty-icon {
|
|
859
|
+
border-radius: 0 !important;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
.omni-kbd,
|
|
863
|
+
.omni-kbd-sm {
|
|
864
|
+
border: 1px solid var(--color-fd-border, hsl(0 0% 15%)) !important;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
.omni-group-label,
|
|
868
|
+
.omni-footer-inner {
|
|
869
|
+
font-family: var(--fd-font-mono, ui-monospace, monospace) !important;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
.omni-search-input {
|
|
873
|
+
font-family: var(--fd-font-sans, system-ui, -apple-system, sans-serif) !important;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
.omni-group-label {
|
|
877
|
+
letter-spacing: 0.08em !important;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.omni-footer-hint svg {
|
|
881
|
+
border-radius: 0 !important;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
.omni-filter-menu,
|
|
885
|
+
.omni-filter-option {
|
|
886
|
+
border-radius: 0 !important;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
.omni-highlight {
|
|
890
|
+
background: color-mix(in srgb, var(--color-fd-primary, #6366f1) 25%, transparent) !important;
|
|
891
|
+
border-radius: 0 !important;
|
|
892
|
+
}
|
|
893
|
+
|
|
844
894
|
/* ─── Feedback (pixel-border theme) ──────────────────────────────── */
|
|
845
895
|
|
|
846
896
|
.fd-feedback-input,
|