Skip to content
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions app/components/AppHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ const isMobile = useIsMobile()
const isSearchExpandedManually = shallowRef(false)
const searchBoxRef = useTemplateRef('searchBoxRef')

const { searchQuery, updateSearchQuery } = usePackageSearchQuery()

const router = useRouter()
function handleSubmitSearch() {
if (searchQuery.value === '') {
return
}

router.replace({
name: 'search',
query: { ...route.query, q: searchQuery.value },
})
}

// On search page, always show search expanded on mobile
const isOnHomePage = computed(() => route.name === 'index')
const isOnSearchPage = computed(() => route.name === 'search')
Expand Down Expand Up @@ -90,7 +104,7 @@ onKeyStroke(
<div class="absolute inset-0 bg-bg/80 backdrop-blur-md" />
<nav
:aria-label="$t('nav.main_navigation')"
class="relative container min-h-14 flex items-center gap-2 z-1"
class="relative container min-h-14 flex items-center gap-4 z-1"
:class="isOnHomePage ? 'justify-end' : 'justify-between'"
>
<!-- Mobile: Logo + search button (expands search, doesn't navigate) -->
Expand Down Expand Up @@ -121,18 +135,20 @@ onKeyStroke(
<span v-else class="hidden sm:block w-1" />

<!-- Center: Search bar + nav items -->
<div
class="flex-1 flex items-center justify-center md:gap-6"
:class="{ 'hidden sm:flex': !isSearchExpanded }"
>
<div class="flex-1 flex max-w-md md:gap-6" :class="{ 'hidden sm:flex': !isSearchExpanded }">
<!-- Search bar (hidden on mobile unless expanded) -->
<HeaderSearchBox
<SearchBox
v-if="!isOnHomePage"
ref="searchBoxRef"
:inputClass="isSearchExpanded ? 'w-full' : ''"
:class="{ 'max-w-md': !isSearchExpanded }"
class="max-w-sm"
compact
:model-value="searchQuery"
@update:model-value="updateSearchQuery"
@submit="handleSubmitSearch"
@focus="handleSearchFocus"
@blur="handleSearchBlur"
/>
Comment thread
coderabbitai[bot] marked this conversation as resolved.

<ul
v-if="!isSearchExpanded && isConnected && npmUser"
:class="{ hidden: showFullSearch }"
Expand Down
134 changes: 0 additions & 134 deletions app/components/Header/SearchBox.vue

This file was deleted.

95 changes: 95 additions & 0 deletions app/components/SearchBox.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<script setup lang="ts">
defineProps<{
compact?: boolean
}>()

const emit = defineEmits<{
(e: 'submit', searchQuery: string): void
(e: 'blur'): void
(e: 'focus'): void
}>()

const searchQuery = defineModel<string>({
default: '',
})
Comment thread
MatteoGabriele marked this conversation as resolved.

function handleSubmit() {
emit('submit', searchQuery.value)
}

function handleBlur() {
emit('blur')
}
function handleFocus() {
emit('focus')
}

// Expose focus method for parent components
const inputRef = useTemplateRef('inputRef')
function focus() {
inputRef.value?.focus()
}

defineExpose({
focus,
})
</script>

<template>
<search class="w-full @container">
<form method="GET" action="/search" class="relative" @submit.prevent="handleSubmit">
<label for="search-box" class="sr-only">
{{ $t('search.label') }}
Comment thread
ghostdevv marked this conversation as resolved.
</label>

<div class="relative group">
<div
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"
/>

<div class="search-box relative flex items-center">
<span
class="absolute text-fg-subtle font-mono 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"
:class="compact ? 'inset-is-3 text-sm' : 'inset-is-4 text-xl'"
>
/
</span>

<input
id="search-box"
ref="inputRef"
v-model.trim="searchQuery"
type="search"
name="q"
:placeholder="$t('search.placeholder')"
Comment thread
ghostdevv marked this conversation as resolved.
v-bind="noCorrect"
class="w-full bg-bg-subtle border border-border text-base font-mono text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 motion-reduce:transition-none hover:border-fg-subtle outline-2 outline-transparent focus:border-accent focus-visible:(outline-2 outline-accent/70)"
:class="
compact ? 'ps-7 pe-3 py-1.5 rounded-md text-sm!' : 'ps-8 pe-24 h-14 py-4 rounded-xl'
"
@blur="handleBlur"
@focus="handleFocus"
/>

<button
type="submit"
class="absolute hidden @xs:block group inset-ie-2.5 font-mono text-sm transition-[background-color,transform] duration-200 active:scale-95"
:class="
compact
? 'px-1.5 py-0.5 @md:ps-4 @md:pe-4'
: 'rounded-md px-2.5 @md:ps-4 @md:pe-4 py-2 text-bg bg-fg/90 hover:bg-fg! group-focus-within:bg-fg/80'
"
>
<span
class="inline-block i-carbon:search align-middle w-4 h-4 @md:me-2"
aria-hidden="true"
></span>
<span class="sr-only @md:not-sr-only">
{{ $t('search.button') }}
Comment thread
ghostdevv marked this conversation as resolved.
</span>
</button>
</div>
</div>
</form>
</search>
</template>
3 changes: 3 additions & 0 deletions app/composables/npm/useNpmSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function packumentToSearchResult(
export interface NpmSearchOptions {
/** Number of results to fetch */
size?: number
onSuccess?: (result: { query: string }) => void
}

export const emptySearchResponse = {
Expand Down Expand Up @@ -151,6 +152,8 @@ export function useNpmSearch(
// Success - clear rate limit flag
isRateLimited.value = false

opts.onSuccess?.({ query: q })

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
return { ...response, isStale }
} catch (error: unknown) {
// Detect rate limit errors. npm's 429 response doesn't include CORS headers,
Expand Down
21 changes: 21 additions & 0 deletions app/composables/usePackageSearchQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { debounce } from 'perfect-debounce'

export function usePackageSearchQuery() {
const searchQuery = useState<string>('package_search_query', () => '')

const route = useRoute()
onMounted(() => {
if (route.query.q) {
searchQuery.value = normalizeSearchParam(route.query.q)
}
})

const updateSearchQuery = debounce((newSearchQuery: string) => {
searchQuery.value = newSearchQuery
}, 400)

return {
searchQuery,
updateSearchQuery,
}
}
Comment on lines +1 to +21
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Search for normalizeSearchParam definition and usage
rg "normalizeSearchParam" --type=ts --type=vue -B2 -A2

Repository: npmx-dev/npmx.dev

Length of output: 88


🏁 Script executed:

# Check the actual imports in usePackageSearchQuery.ts
cat app/composables/usePackageSearchQuery.ts

Repository: npmx-dev/npmx.dev

Length of output: 541


🏁 Script executed:

# Check nuxt.config for auto-import configuration
fd "nuxt.config" -type f | xargs cat

Repository: npmx-dev/npmx.dev

Length of output: 231


🏁 Script executed:

# Search for normalizeSearchParam definition
rg "normalizeSearchParam" -A3 -B3

Repository: npmx-dev/npmx.dev

Length of output: 5756


🏁 Script executed:

# Find nuxt.config files
find . -name "nuxt.config*" -type f

Repository: npmx-dev/npmx.dev

Length of output: 99


🏁 Script executed:

# Check for utils/url.ts where normalizeSearchParam might be defined
find app -name "url.ts" -o -name "url.js"

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

# Check nuxt.config for auto-import configuration
cat -n nuxt.config.ts

Repository: npmx-dev/npmx.dev

Length of output: 9567


🏁 Script executed:

# Check the useStructuredFilters composable which also uses normalizeSearchParam
cat app/composables/useStructuredFilters.ts

Repository: npmx-dev/npmx.dev

Length of output: 14869


Add missing import for normalizeSearchParam.

The composable uses normalizeSearchParam (line 8) without importing it. Add import { normalizeSearchParam } from '#shared/utils/url' at the top of the file.

Loading
Loading