refactor(ui): new InputGroup primitive + QueryIP switches to it

InputGroup owns border / rounded / shadow / focus-within:ring at the
wrapper level; inside, the [&>input] / [&>button] permeating selectors
flatten child elements' independent border / shadow / ring / rounded.
No more hand-written rounded-r-none / rounded-l-none / -ml-px border-
collapse hacks.

QueryIP is the first beneficiary; 5 more Input+Button stitching sites
in advanced-tools/ (DnsResolver / CensorshipCheck / Whois / GlobalLatencyTest)
will migrate later together.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jason5ng32andClaude Opus 4.7 committed 2026-04-17 19:07:09 +08:00
1 parent 9f5c80172d
commit 590a4aa5e3
3 files changed
+42 -5

No files matched your search

@@ -0,0 +1,36 @@
<script setup>
// shadcn-vue Input Group:把 Input + Button(或 Input + addon)拼成一个
// 视觉一体的输入条。wrapper 自己承担 border / rounded / shadow / focus-ring,
// 用 [&>*] 透穿选择器把内部 <input> / <button> 的独立边框/阴影/ring 扁平化。
//
// 用法:
// <InputGroup>
// <Input v-model="q" placeholder="..." />
// <Button :disabled="..." @click="...">Go</Button>
// </InputGroup>
//
// 需要分隔线时:<InputGroup class="[&>*:not(:first-child)]:border-l [&>*:not(:first-child)]:border-input" />
import { cn } from '@/lib/utils';
defineProps({
class: { type: [String, Array, Object], default: undefined },
});
</script>
<template>
<div
:class="cn(
'flex items-stretch w-full rounded-md border border-input bg-background shadow-sm overflow-hidden transition-colors',
'focus-within:ring-1 focus-within:ring-ring',
// 扁平化所有 input / button 子元素:取消它们各自的 border / shadow / ring / rounded,
// 让整个 group 看起来是一个整体
'[&>input]:border-0 [&>input]:shadow-none [&>input]:rounded-none [&>input]:bg-transparent',
'[&>input]:focus-visible:ring-0 [&>input]:focus-visible:outline-none',
'[&>button]:border-0 [&>button]:shadow-none [&>button]:rounded-none',
'[&>button]:focus-visible:ring-0 [&>button]:focus-visible:outline-none',
$props.class,
)"
>
<slot />
</div>
</template>
@@ -0,0 +1 @@
export { default as InputGroup } from './InputGroup.vue';
+5 -5
View File
@@ -15,19 +15,18 @@
<DialogHeader :icon="Search" :title="t('ipcheck.Title')" />
<div class="space-y-4">
<!-- 输入 + 查询按钮(border-collapse 拼接) -->
<div class="flex">
<!-- Input Group:整条输入条由 group 承担 border / rounded / focus-ring,
内部 Input / Button 的独立样式被 group 扁平化,不再需要 rounded-r-none / -ml-px 拼接 -->
<InputGroup>
<Input type="text" id="inputIP" name="inputIP"
class="rounded-r-none"
:placeholder="t('ipcheck.Placeholder')"
v-model="inputIP" @keyup.enter="submitQuery" />
<Button id="sumitQueryButton" type="button"
class="rounded-l-none -ml-px shrink-0"
:disabled="!isValidIP(inputIP) || isChecking === 'running'"
@click="submitQuery">
{{ t('ipcheck.Button') }}
</Button>
</div>
</InputGroup>
<!-- 错误提示 -->
<p v-if="modalQueryError" class="text-sm text-destructive">{{ modalQueryError }}</p>
@@ -179,6 +178,7 @@ import { Dialog, DialogContent, DialogHeader } from '@/components/ui/dialog';
import { JnTooltip } from '@/components/ui/tooltip';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { InputGroup } from '@/components/ui/input-group';
import { Progress } from '@/components/ui/progress';
import { Icon } from '@iconify/vue';
import {