Skip to content

Commit ec394e5

Browse files
refactor: search happens on enter
1 parent 4c8fe76 commit ec394e5

4 files changed

Lines changed: 21 additions & 67 deletions

File tree

app/assets/main.css

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -280,11 +280,6 @@ p > span > code,
280280
border: 1px solid var(--border);
281281
}
282282

283-
/* View transition for search box (includes / and input) */
284-
.search-box {
285-
view-transition-name: search-box;
286-
}
287-
288283
/* Safari search input fixes */
289284
input[type='search'] {
290285
-webkit-appearance: none;
@@ -313,13 +308,6 @@ input[type='search']::-webkit-search-results-decoration {
313308
animation: none;
314309
}
315310

316-
/* Customize the view transition animations for specific elements */
317-
::view-transition-old(search-box),
318-
::view-transition-new(search-box) {
319-
animation-duration: 0.3s;
320-
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
321-
}
322-
323311
/* Locking the scroll whenever any of the modals are open */
324312
html:has(dialog:modal) {
325313
overflow: hidden;

app/components/Header/SearchBox.vue

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<script setup lang="ts">
2-
import { debounce } from 'perfect-debounce'
32
import { normalizeSearchParam } from '#shared/utils/url'
43
54
withDefaults(
@@ -22,12 +21,14 @@ const showSearchBar = computed(() => {
2221
2322
// Local input value (updates immediately as user types)
2423
const searchQuery = shallowRef('')
24+
onMounted(() => {
25+
searchQuery.value = normalizeSearchParam(route.query.q)
26+
})
2527
2628
// Pages that have their own local filter using ?q
2729
const pagesWithLocalFilter = new Set(['~username', 'org'])
2830
29-
// Debounced URL update for search query
30-
const updateUrlQuery = debounce((value: string) => {
31+
const updateUrlQuery = (value: string) => {
3132
// Don't navigate away from pages that use ?q for local filtering
3233
if (pagesWithLocalFilter.has(route.name as string)) {
3334
return
@@ -46,28 +47,7 @@ const updateUrlQuery = debounce((value: string) => {
4647
q: value,
4748
},
4849
})
49-
}, 250)
50-
51-
// Sync input with URL when navigating (e.g., back button)
52-
watch(
53-
() => route.query.q,
54-
urlQuery => {
55-
// Don't sync from pages that use ?q for local filtering
56-
if (pagesWithLocalFilter.has(route.name as string)) {
57-
return
58-
}
59-
const value = normalizeSearchParam(urlQuery)
60-
if (searchQuery.value !== value) {
61-
searchQuery.value = value
62-
}
63-
},
64-
{ immediate: true },
65-
)
66-
67-
// Watch input and debounce URL updates
68-
watch(searchQuery, value => {
69-
updateUrlQuery(value)
70-
})
50+
}
7151
7252
function handleSearchBlur() {
7353
emit('blur')
@@ -76,6 +56,8 @@ function handleSearchFocus() {
7656
emit('focus')
7757
}
7858
59+
const inputRef = useTemplateRef('inputRef')
60+
7961
function handleSubmit() {
8062
if (pagesWithLocalFilter.has(route.name as string)) {
8163
router.push({
@@ -85,12 +67,13 @@ function handleSubmit() {
8567
},
8668
})
8769
} else {
88-
updateUrlQuery.flush()
70+
updateUrlQuery(searchQuery.value)
8971
}
72+
73+
inputRef.value?.focus()
9074
}
9175
9276
// Expose focus method for parent components
93-
const inputRef = useTemplateRef('inputRef')
9477
function focus() {
9578
inputRef.value?.focus()
9679
}
@@ -103,8 +86,10 @@ defineExpose({ focus })
10386
{{ $t('search.label') }}
10487
</label>
10588

106-
<div class="relative group">
107-
<div class="search-box relative flex items-center">
89+
<div
90+
class="relative group bg-bg-subtle border border-border rounded-md transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent focus-within:border-accent focus-visible:(outline-2 outline-accent/70)"
91+
>
92+
<div class="flex items-center">
10893
<span
10994
class="absolute inset-is-3 text-fg-subtle font-mono text-sm pointer-events-none transition-colors duration-200 motion-reduce:transition-none [.group:hover:not(:focus-within)_&]:text-fg/80 group-focus-within:text-accent z-1"
11095
>
@@ -119,11 +104,15 @@ defineExpose({ focus })
119104
name="q"
120105
:placeholder="$t('search.placeholder')"
121106
v-bind="noCorrect"
122-
class="w-full min-w-25 bg-bg-subtle border border-border rounded-md ps-7 pe-3 py-1.5 font-mono text-sm text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent focus:border-accent focus-visible:(outline-2 outline-accent/70)"
107+
class="w-full ps-7 pe-3 py-1.5 font-mono text-sm text-fg placeholder:text-fg-subtle outline-none"
123108
@focus="handleSearchFocus"
124109
@blur="handleSearchBlur"
125110
/>
126-
<button type="submit" class="sr-only">{{ $t('search.button') }}</button>
111+
112+
<button type="submit" class="flex items-center justify-center p-2">
113+
<span class="sr-only">{{ $t('search.button') }}</span>
114+
<span class="i-carbon-search w-4 h-4" aria-hidden="true" />
115+
</button>
127116
</div>
128117
</div>
129118
</form>

app/pages/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ defineOgImageComponent('Default', {
6969
class="absolute -inset-px rounded-lg bg-gradient-to-r from-fg/0 via-fg/5 to-fg/0 opacity-0 transition-opacity duration-500 blur-sm group-[.is-focused]:opacity-100"
7070
/>
7171

72-
<div class="search-box relative flex items-center">
72+
<div class="relative flex items-center">
7373
<span
7474
class="absolute inset-is-4 text-fg-subtle font-mono text-lg pointer-events-none transition-colors duration-200 motion-reduce:transition-none [.group:hover:not(:focus-within)_&]:text-fg/80 group-focus-within:text-accent z-1"
7575
>

app/pages/search.vue

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -540,29 +540,6 @@ async function navigateToPackage(packageName: string) {
540540
// Track the input value when user pressed Enter (for navigating when results arrive)
541541
const pendingEnterQuery = shallowRef<string | null>(null)
542542
543-
// Watch for results to navigate when Enter was pressed before results arrived
544-
watch(displayResults, results => {
545-
if (!pendingEnterQuery.value) return
546-
547-
// Check if input is still focused (user hasn't started navigating or clicked elsewhere)
548-
if (document.activeElement?.tagName !== 'INPUT') {
549-
pendingEnterQuery.value = null
550-
return
551-
}
552-
553-
// Navigate if first result matches the query that was entered
554-
const firstResult = results[0]
555-
// eslint-disable-next-line no-console
556-
console.log('[search] watcher fired', {
557-
pending: pendingEnterQuery.value,
558-
firstResult: firstResult?.package.name,
559-
})
560-
if (firstResult?.package.name === pendingEnterQuery.value) {
561-
pendingEnterQuery.value = null
562-
navigateToPackage(firstResult.package.name)
563-
}
564-
})
565-
566543
function handleResultsKeydown(e: KeyboardEvent) {
567544
// If the active element is an input, navigate to exact match or wait for results
568545
if (e.key === 'Enter' && document.activeElement?.tagName === 'INPUT') {

0 commit comments

Comments
 (0)