From aad32c3230def1b2e518056318f9e85f49683da8 Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sat, 7 Feb 2026 19:40:28 +0000 Subject: [PATCH 1/9] feat: add and use input component --- app/components/AppHeader.vue | 8 ++- app/components/Compare/PackageSelector.vue | 13 ++--- app/components/Filter/Panel.vue | 6 ++- app/components/Header/AuthModal.client.vue | 7 +-- app/components/Header/ConnectorModal.vue | 12 +++-- app/components/Header/SearchBox.vue | 17 ++----- app/components/Input/Base.vue | 51 ++++++++++++++++++++ app/components/Org/MembersPanel.vue | 16 +++--- app/components/Org/OperationsQueue.vue | 7 +-- app/components/Org/TeamsPanel.vue | 27 ++++++----- app/components/Package/AccessControls.vue | 2 +- app/components/Package/DownloadAnalytics.vue | 28 ++++++----- app/components/Package/ListControls.vue | 9 ++-- app/components/Package/Maintainers.vue | 7 +-- app/pages/index.vue | 15 +++--- 15 files changed, 144 insertions(+), 81 deletions(-) create mode 100644 app/components/Input/Base.vue diff --git a/app/components/AppHeader.vue b/app/components/AppHeader.vue index 01390c82e..c59bdd132 100644 --- a/app/components/AppHeader.vue +++ b/app/components/AppHeader.vue @@ -122,8 +122,12 @@ onKeyStroke(
-
+
-
diff --git a/app/components/Header/AuthModal.client.vue b/app/components/Header/AuthModal.client.vue index d12a8e528..f25bbc8cc 100644 --- a/app/components/Header/AuthModal.client.vue +++ b/app/components/Header/AuthModal.client.vue @@ -72,14 +72,15 @@ watch(handleInput, newHandleInput => { > {{ $t('auth.modal.handle_label') }} -
diff --git a/app/components/Header/ConnectorModal.vue b/app/components/Header/ConnectorModal.vue index b5b30218c..d37f872e0 100644 --- a/app/components/Header/ConnectorModal.vue +++ b/app/components/Header/ConnectorModal.vue @@ -161,14 +161,15 @@ function handleDisconnect() { > {{ $t('connector.modal.token_label') }} - @@ -185,14 +186,15 @@ function handleDisconnect() { > {{ $t('connector.modal.port_label') }} - diff --git a/app/components/Header/SearchBox.vue b/app/components/Header/SearchBox.vue index b99fa79cb..0bcdf85f5 100644 --- a/app/components/Header/SearchBox.vue +++ b/app/components/Header/SearchBox.vue @@ -70,15 +70,6 @@ watch( }, ) -function handleSearchBlur() { - isSearchFocused.value = false - emit('blur') -} -function handleSearchFocus() { - isSearchFocused.value = true - emit('focus') -} - function handleSubmit() { if (pagesWithLocalFilter.has(route.name as string)) { router.push({ @@ -114,17 +105,15 @@ defineExpose({ focus }) / - diff --git a/app/components/Input/Base.vue b/app/components/Input/Base.vue new file mode 100644 index 000000000..a8e6b3657 --- /dev/null +++ b/app/components/Input/Base.vue @@ -0,0 +1,51 @@ + + + diff --git a/app/components/Org/MembersPanel.vue b/app/components/Org/MembersPanel.vue index 5ad15717b..403307a22 100644 --- a/app/components/Org/MembersPanel.vue +++ b/app/components/Org/MembersPanel.vue @@ -331,14 +331,15 @@ watch(lastExecutionTime, () => { aria-hidden="true" /> -
{ -
@@ -553,7 +555,7 @@ watch(lastExecutionTime, () => { diff --git a/app/components/Org/OperationsQueue.vue b/app/components/Org/OperationsQueue.vue index 6d3732058..f9c4e891b 100644 --- a/app/components/Org/OperationsQueue.vue +++ b/app/components/Org/OperationsQueue.vue @@ -242,7 +242,7 @@ watch(isExecuting, executing => {
- { :placeholder="$t('operations.queue.otp_placeholder')" autocomplete="one-time-code" spellcheck="false" - class="flex-1 px-3 py-1.5 font-mono text-sm bg-bg border border-border rounded text-fg placeholder:text-fg-subtle transition-colors duration-200 focus:border-border-hover focus-visible:outline-accent/70" + class="flex-1 min-w-25" + size="small" /> diff --git a/app/components/Org/TeamsPanel.vue b/app/components/Org/TeamsPanel.vue index 095aa5cf2..1eabf8932 100644 --- a/app/components/Org/TeamsPanel.vue +++ b/app/components/Org/TeamsPanel.vue @@ -287,14 +287,15 @@ watch(lastExecutionTime, () => { aria-hidden="true" /> -
{ > ~{{ user }} - {{ teamName }} + {{ teamName }} diff --git a/app/components/Package/AccessControls.vue b/app/components/Package/AccessControls.vue index c3d9a7d89..901437416 100644 --- a/app/components/Package/AccessControls.vue +++ b/app/components/Package/AccessControls.vue @@ -281,7 +281,7 @@ watch( diff --git a/app/components/Package/DownloadAnalytics.vue b/app/components/Package/DownloadAnalytics.vue index 4b3fd02f4..5edd8b14e 100644 --- a/app/components/Package/DownloadAnalytics.vue +++ b/app/components/Package/DownloadAnalytics.vue @@ -992,16 +992,18 @@ const chartConfig = computed(() => { > {{ $t('package.downloads.start_date') }} -
-
@@ -1013,16 +1015,18 @@ const chartConfig = computed(() => { > {{ $t('package.downloads.end_date') }} -
-
diff --git a/app/components/Package/ListControls.vue b/app/components/Package/ListControls.vue index 1b31f318b..56cce6a3b 100644 --- a/app/components/Package/ListControls.vue +++ b/app/components/Package/ListControls.vue @@ -61,13 +61,14 @@ const showFilteredCount = computed(() => { >
- @@ -78,7 +79,7 @@ const showFilteredCount = computed(() => { {{ isAdding ? '…' : $t('package.maintainers.add_button') }} diff --git a/app/pages/index.vue b/app/pages/index.vue index 88395fee5..0922652f1 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -3,8 +3,7 @@ import { debounce } from 'perfect-debounce' import { SHOWCASED_FRAMEWORKS } from '~/utils/frameworks' const searchQuery = shallowRef('') -const searchInputRef = useTemplateRef('searchInputRef') -const { focused: isSearchFocused } = useFocus(searchInputRef) +const isSearchFocused = shallowRef(false) async function search() { const query = searchQuery.value.trim() @@ -73,7 +72,7 @@ defineOgImageComponent('Default', {
diff --git a/app/components/Input/Base.vue b/app/components/Input/Base.vue index 55ab70e7d..95eb9053d 100644 --- a/app/components/Input/Base.vue +++ b/app/components/Input/Base.vue @@ -41,7 +41,7 @@ defineExpose({ v-bind="props.noCorrect ? noCorrect : undefined" @focus="emit('focus', $event)" @blur="emit('blur', $event)" - class="w-full leading-none bg-bg-subtle border border-border font-mono text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent outline-offset-2 focus:border-accent focus-visible:outline-accent/70 disabled:(opacity-50 cursor-not-allowed)" + class="leading-none bg-bg-subtle border border-border font-mono text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent outline-offset-2 focus:border-accent focus-visible:outline-accent/70 disabled:(opacity-50 cursor-not-allowed)" :class="{ 'text-xs px-2 py-1.25 h-8 rounded-md': size === 'small', 'text-sm px-3 py-2.5 h-10 rounded-lg': size === 'medium', diff --git a/app/components/Org/TeamsPanel.vue b/app/components/Org/TeamsPanel.vue index 1eabf8932..fdb2adf65 100644 --- a/app/components/Org/TeamsPanel.vue +++ b/app/components/Org/TeamsPanel.vue @@ -504,6 +504,7 @@ watch(lastExecutionTime, () => { {{ orgName }}: + { name="new-team-name" :placeholder="$t('org.teams.team_name_placeholder')" noCorrect - class="flex-1 min-w-25 rounded-l-none" + class="flex-1 min-w-25 rounded-is-none" size="medium" />
From bfaec20d7c1303ed6ab5a7d522c27e51dc12712a Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sat, 7 Feb 2026 21:29:02 +0000 Subject: [PATCH 4/9] feat: remove comment --- app/components/Org/TeamsPanel.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/app/components/Org/TeamsPanel.vue b/app/components/Org/TeamsPanel.vue index fdb2adf65..eefa9e149 100644 --- a/app/components/Org/TeamsPanel.vue +++ b/app/components/Org/TeamsPanel.vue @@ -504,7 +504,6 @@ watch(lastExecutionTime, () => { {{ orgName }}: - Date: Sat, 7 Feb 2026 22:11:53 +0000 Subject: [PATCH 5/9] feat: update input-base disabled behavior --- app/components/Input/Base.vue | 5 +++++ test/nuxt/components/Input/Base.spec.ts | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/components/Input/Base.vue b/app/components/Input/Base.vue index 95eb9053d..9a0013fb8 100644 --- a/app/components/Input/Base.vue +++ b/app/components/Input/Base.vue @@ -3,6 +3,7 @@ import { noCorrect } from '~/utils/input' const props = withDefaults( defineProps<{ + disabled?: boolean modelValue?: string size?: 'small' | 'medium' | 'large' noCorrect?: boolean @@ -47,5 +48,9 @@ defineExpose({ 'text-sm px-3 py-2.5 h-10 rounded-lg': size === 'medium', 'text-base px-6 py-3.5 h-14 rounded-xl': size === 'large', }" + :disabled=" + /** Catching Vue render-bug of invalid `disabled=false` attribute in the final HTML */ + disabled ? true : undefined + " /> diff --git a/test/nuxt/components/Input/Base.spec.ts b/test/nuxt/components/Input/Base.spec.ts index 873a9bda8..98d650017 100644 --- a/test/nuxt/components/Input/Base.spec.ts +++ b/test/nuxt/components/Input/Base.spec.ts @@ -146,8 +146,6 @@ describe('InputBase', () => { const input = component.find('input') expect(input.attributes('disabled')).toBeDefined() expect((input.element as HTMLInputElement).disabled).toBe(true) - // should add just `disabled`, not `disabled="true"` - expect((input.element as HTMLInputElement).getHTML()).not.toContain('disabled=') }) }) }) From efa1d8e9a2bbb6235355eff46c8bfeb9b6aeeddc Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sun, 8 Feb 2026 12:27:44 +0000 Subject: [PATCH 6/9] feat: improve downloads filters ui --- app/components/Package/DownloadAnalytics.vue | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/components/Package/DownloadAnalytics.vue b/app/components/Package/DownloadAnalytics.vue index b452d0674..eea3bd44a 100644 --- a/app/components/Package/DownloadAnalytics.vue +++ b/app/components/Package/DownloadAnalytics.vue @@ -1488,7 +1488,7 @@ const chartConfig = computed(() => { id="granularity" v-model="selectedGranularity" :disabled="pending" - class="w-full px-2.5 py-1.75 bg-bg-subtle font-mono text-sm text-fg outline-none appearance-none focus-visible:outline-accent/70" + class="w-full px-4 py-3 leading-none bg-bg-subtle font-mono text-sm text-fg outline-none appearance-none focus-visible:outline-accent/70" > @@ -1508,7 +1508,7 @@ const chartConfig = computed(() => {
@@ -1528,7 +1528,7 @@ const chartConfig = computed(() => {
From 62def3e8f0fcc837b5f1095817901b900cb05ffe Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sun, 8 Feb 2026 16:21:23 +0000 Subject: [PATCH 7/9] feat: fix base-input sizes for scaling --- app/components/Header/SearchBox.vue | 1 + app/components/Input/Base.vue | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/components/Header/SearchBox.vue b/app/components/Header/SearchBox.vue index 52f9723ba..19960f028 100644 --- a/app/components/Header/SearchBox.vue +++ b/app/components/Header/SearchBox.vue @@ -116,6 +116,7 @@ defineExpose({ focus }) class="w-full min-w-25 ps-7" @focus="isSearchFocused = true" @blur="isSearchFocused = false" + size="small" /> diff --git a/app/components/Input/Base.vue b/app/components/Input/Base.vue index 9a0013fb8..7ac5451f6 100644 --- a/app/components/Input/Base.vue +++ b/app/components/Input/Base.vue @@ -42,11 +42,11 @@ defineExpose({ v-bind="props.noCorrect ? noCorrect : undefined" @focus="emit('focus', $event)" @blur="emit('blur', $event)" - class="leading-none bg-bg-subtle border border-border font-mono text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent outline-offset-2 focus:border-accent focus-visible:outline-accent/70 disabled:(opacity-50 cursor-not-allowed)" + class="bg-bg-subtle border border-border font-mono text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent outline-offset-2 focus:border-accent focus-visible:outline-accent/70 disabled:(opacity-50 cursor-not-allowed)" :class="{ - 'text-xs px-2 py-1.25 h-8 rounded-md': size === 'small', - 'text-sm px-3 py-2.5 h-10 rounded-lg': size === 'medium', - 'text-base px-6 py-3.5 h-14 rounded-xl': size === 'large', + 'text-xs leading-[1.2] px-2 py-2 rounded-md': size === 'small', + 'text-sm leading-none px-3 py-2.5 rounded-lg': size === 'medium', + 'text-base leading-none px-6 py-3.5 h-14 rounded-xl': size === 'large', }" :disabled=" /** Catching Vue render-bug of invalid `disabled=false` attribute in the final HTML */ From b3c164a2a1184e2e93cc305414d6b6e7a7df9764 Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sun, 8 Feb 2026 17:05:19 +0000 Subject: [PATCH 8/9] test: get rid of search icon in package-selector --- test/nuxt/components/compare/PackageSelector.spec.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/nuxt/components/compare/PackageSelector.spec.ts b/test/nuxt/components/compare/PackageSelector.spec.ts index b334eab03..5b004f420 100644 --- a/test/nuxt/components/compare/PackageSelector.spec.ts +++ b/test/nuxt/components/compare/PackageSelector.spec.ts @@ -119,16 +119,6 @@ describe('PackageSelector', () => { input = component.find('input') expect(input.attributes('placeholder')).toBeTruthy() }) - - it('has search icon', async () => { - const component = await mountSuspended(PackageSelector, { - props: { - modelValue: [], - }, - }) - - expect(component.find('.i-carbon\\:search').exists()).toBe(true) - }) }) describe('adding packages', () => { From 549026aeac4a32420ff0533993a30cf4a0c19d0d Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sun, 8 Feb 2026 18:31:07 +0000 Subject: [PATCH 9/9] feat: use kebab-case and define-model in input --- app/components/Compare/PackageSelector.vue | 2 +- app/components/Filter/Panel.vue | 2 +- app/components/Header/AuthModal.client.vue | 2 +- app/components/Header/ConnectorModal.vue | 2 +- app/components/Header/SearchBox.vue | 2 +- app/components/Input/Base.vue | 15 ++++----------- app/components/Org/MembersPanel.vue | 4 ++-- app/components/Org/TeamsPanel.vue | 6 +++--- app/components/Package/ListControls.vue | 2 +- app/components/Package/Maintainers.vue | 2 +- app/pages/index.vue | 2 +- test/nuxt/components/Input/Base.spec.ts | 8 -------- 12 files changed, 17 insertions(+), 32 deletions(-) diff --git a/app/components/Compare/PackageSelector.vue b/app/components/Compare/PackageSelector.vue index b2c8aaba5..9c31395e7 100644 --- a/app/components/Compare/PackageSelector.vue +++ b/app/components/Compare/PackageSelector.vue @@ -157,7 +157,7 @@ function handleBlur() { ? $t('compare.selector.search_first') : $t('compare.selector.search_add') " - noCorrect + no-correct size="medium" class="w-full min-w-25 ps-7" aria-autocomplete="list" diff --git a/app/components/Filter/Panel.vue b/app/components/Filter/Panel.vue index 7cade4f4e..52b6c6338 100644 --- a/app/components/Filter/Panel.vue +++ b/app/components/Filter/Panel.vue @@ -247,7 +247,7 @@ const hasActiveFilters = computed(() => !!filterSummary.value) autocomplete="off" class="w-full min-w-25" size="medium" - noCorrect + no-correct @input="handleTextInput" /> diff --git a/app/components/Header/AuthModal.client.vue b/app/components/Header/AuthModal.client.vue index 68bdee6a4..caab42736 100644 --- a/app/components/Header/AuthModal.client.vue +++ b/app/components/Header/AuthModal.client.vue @@ -95,7 +95,7 @@ watch(handleInput, newHandleInput => { type="text" name="handle" :placeholder="$t('auth.modal.handle_placeholder')" - noCorrect + no-correct class="w-full" size="medium" /> diff --git a/app/components/Header/ConnectorModal.vue b/app/components/Header/ConnectorModal.vue index a078bb4b2..0210462ac 100644 --- a/app/components/Header/ConnectorModal.vue +++ b/app/components/Header/ConnectorModal.vue @@ -167,7 +167,7 @@ function handleDisconnect() { type="password" name="connector-token" :placeholder="$t('connector.modal.token_placeholder')" - noCorrect + no-correct class="w-full" size="medium" /> diff --git a/app/components/Header/SearchBox.vue b/app/components/Header/SearchBox.vue index 19960f028..2822fd2b1 100644 --- a/app/components/Header/SearchBox.vue +++ b/app/components/Header/SearchBox.vue @@ -112,7 +112,7 @@ defineExpose({ focus }) type="search" name="q" :placeholder="$t('search.placeholder')" - noCorrect + no-correct class="w-full min-w-25 ps-7" @focus="isSearchFocused = true" @blur="isSearchFocused = false" diff --git a/app/components/Input/Base.vue b/app/components/Input/Base.vue index 7ac5451f6..49b971b66 100644 --- a/app/components/Input/Base.vue +++ b/app/components/Input/Base.vue @@ -1,37 +1,30 @@ diff --git a/app/components/Org/MembersPanel.vue b/app/components/Org/MembersPanel.vue index 403307a22..2da3ff55f 100644 --- a/app/components/Org/MembersPanel.vue +++ b/app/components/Org/MembersPanel.vue @@ -337,7 +337,7 @@ watch(lastExecutionTime, () => { type="search" name="members-search" :placeholder="$t('org.members.filter_placeholder')" - noCorrect + no-correct class="w-full min-w-25 ps-7" size="small" /> @@ -523,7 +523,7 @@ watch(lastExecutionTime, () => { type="text" name="new-member-username" :placeholder="$t('org.members.username_placeholder')" - noCorrect + no-correct class="w-full min-w-25" size="small" /> diff --git a/app/components/Org/TeamsPanel.vue b/app/components/Org/TeamsPanel.vue index eefa9e149..ad378e5a5 100644 --- a/app/components/Org/TeamsPanel.vue +++ b/app/components/Org/TeamsPanel.vue @@ -293,7 +293,7 @@ watch(lastExecutionTime, () => { type="search" name="teams-search" :placeholder="$t('org.teams.filter_placeholder')" - noCorrect + no-correct class="w-full min-w-25 ps-7" size="medium" /> @@ -453,7 +453,7 @@ watch(lastExecutionTime, () => { type="text" :name="`add-user-${teamName}`" :placeholder="$t('org.teams.username_placeholder')" - noCorrect + no-correct class="flex-1 min-w-25" size="medium" /> @@ -510,7 +510,7 @@ watch(lastExecutionTime, () => { type="text" name="new-team-name" :placeholder="$t('org.teams.team_name_placeholder')" - noCorrect + no-correct class="flex-1 min-w-25 rounded-is-none" size="medium" /> diff --git a/app/components/Package/ListControls.vue b/app/components/Package/ListControls.vue index e641a7b75..066845c5c 100644 --- a/app/components/Package/ListControls.vue +++ b/app/components/Package/ListControls.vue @@ -66,7 +66,7 @@ const showFilteredCount = computed(() => { v-model="filterValue" type="search" :placeholder="placeholder ?? $t('package.list.filter_placeholder')" - noCorrect + no-correct class="w-full min-w-25 ps-10" size="medium" /> diff --git a/app/components/Package/Maintainers.vue b/app/components/Package/Maintainers.vue index c0f8d09b2..2d50d768f 100644 --- a/app/components/Package/Maintainers.vue +++ b/app/components/Package/Maintainers.vue @@ -260,7 +260,7 @@ watch( type="text" name="add-owner-username" :placeholder="$t('package.maintainers.username_placeholder')" - noCorrect + no-correct class="flex-1 min-w-25 m-1" size="small" /> diff --git a/app/pages/index.vue b/app/pages/index.vue index d26cfb1d1..66ea291a3 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -89,7 +89,7 @@ defineOgImageComponent('Default', { name="q" autofocus :placeholder="$t('search.placeholder')" - noCorrect + no-correct size="large" class="w-full ps-8 pe-24" @focus="isSearchFocused = true" diff --git a/test/nuxt/components/Input/Base.spec.ts b/test/nuxt/components/Input/Base.spec.ts index 98d650017..f2c3b02fe 100644 --- a/test/nuxt/components/Input/Base.spec.ts +++ b/test/nuxt/components/Input/Base.spec.ts @@ -119,14 +119,6 @@ describe('InputBase', () => { expect(document.activeElement).not.toBe(input.element) container.remove() }) - - it('exposes getBoundingClientRect()', async () => { - const component = await mountSuspended(InputBase) - const rect = component.vm.getBoundingClientRect() - expect(rect).toBeDefined() - expect(typeof rect?.width).toBe('number') - expect(typeof rect?.height).toBe('number') - }) }) describe('accessibility (attrs fallthrough)', () => {