feat: refine channel management UI

This commit is contained in:
CaIon
2026-06-28 16:36:32 +08:00
parent 1d166532fe
commit 25f998595d
19 changed files with 1232 additions and 649 deletions
+20 -19
View File
@@ -43,6 +43,23 @@ type EditorRow = {
value: string
}
function parseJsonRows(json: string): EditorRow[] {
try {
if (!json.trim()) return []
const parsed = JSON.parse(json)
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
return []
}
return Object.entries(parsed).map(([key, val], index) => ({
id: `${Date.now()}-${index}`,
key,
value: typeof val === 'object' ? JSON.stringify(val) : String(val),
}))
} catch {
return []
}
}
export function JsonEditor({
value,
onChange,
@@ -63,27 +80,11 @@ export function JsonEditor({
const resolvedKeyLabel = keyLabel ?? t('Key')
const resolvedValueLabel = valueLabel ?? t('Value')
const [mode, setMode] = useState<'visual' | 'json'>('visual')
const [rows, setRows] = useState<EditorRow[]>([])
const [rows, setRows] = useState<EditorRow[]>(() => parseJsonRows(value))
const [jsonValue, setJsonValue] = useState(value)
const parseJsonToRows = (json: string) => {
try {
if (!json.trim()) {
setRows([])
return
}
const parsed = JSON.parse(json)
const newRows: EditorRow[] = Object.entries(parsed).map(
([key, val], index) => ({
id: `${Date.now()}-${index}`,
key,
value: typeof val === 'object' ? JSON.stringify(val) : String(val),
})
)
setRows(newRows)
} catch (_error) {
// Invalid JSON, keep current rows
}
setRows(parseJsonRows(json))
}
// Parse JSON to rows when value changes externally
@@ -228,7 +229,7 @@ export function JsonEditor({
<div className='grid grid-cols-[1fr_1fr_auto] gap-2 text-sm font-medium'>
<div>{resolvedKeyLabel}</div>
<div>{resolvedValueLabel}</div>
<div className='w-10'></div>
<div className='w-10' />
</div>
{rows.map((row) => (
<div
+13 -1
View File
@@ -37,6 +37,7 @@ interface ComboboxInputProps {
className?: string
id?: string
allowCustomValue?: boolean
openOnFocus?: boolean
}
export function ComboboxInput({
@@ -48,6 +49,7 @@ export function ComboboxInput({
className,
id,
allowCustomValue = false,
openOnFocus = true,
}: ComboboxInputProps) {
const { t } = useTranslation()
const [open, setOpen] = React.useState(false)
@@ -56,6 +58,7 @@ export function ComboboxInput({
const containerRef = React.useRef<HTMLDivElement>(null)
const inputRef = React.useRef<HTMLInputElement>(null)
const listRef = React.useRef<HTMLUListElement>(null)
const pointerFocusRef = React.useRef(false)
const selectedOption = React.useMemo(
() => options.find((option) => option.value === value),
[options, value]
@@ -175,9 +178,18 @@ export function ComboboxInput({
}
if (!open) setOpen(true)
}}
onPointerDown={() => {
pointerFocusRef.current = true
if (document.activeElement === inputRef.current && !open) {
setOpen(true)
}
}}
onFocus={() => {
setSearchValue(allowCustomValue && !selectedOption ? value : '')
setOpen(true)
if (openOnFocus || pointerFocusRef.current) {
setOpen(true)
}
pointerFocusRef.current = false
}}
onKeyDown={handleKeyDown}
className={cn('pr-9', className)}
+2
View File
@@ -47,6 +47,7 @@ type LegacyComboboxProps = {
allowCustomValue?: boolean
className?: string
id?: string
openOnFocus?: boolean
}
function Combobox(props: LegacyComboboxProps): React.ReactElement
@@ -69,6 +70,7 @@ function Combobox(
emptyText={props.emptyText}
className={props.className}
allowCustomValue={props.allowCustomValue}
openOnFocus={props.openOnFocus}
/>
)
}