// Shared UI primitives extracted from ConfigPage.tsx import { useState, type ReactNode } from 'react' import { X, Plus, Trash2 } from 'lucide-react' import { inputCls } from './constants' import type { KnownSource } from './types' export function Field({ label, children, hint }: { label: string; children: ReactNode; hint?: string }) { return (
{children} {hint &&

{hint}

}
) } export function Toggle({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) { return ( ) } export function TagEditor({ tags, onChange }: { tags: string[]; onChange: (t: string[]) => void }) { const [input, setInput] = useState('') const add = () => { const v = input.trim(); if (v && !tags.includes(v)) { onChange([...tags, v]); setInput('') } } return (
{tags.map((t, i) => ( {t} ))}
setInput(e.target.value)} onKeyDown={e => e.key === 'Enter' && (e.preventDefault(), add())} placeholder="输入后按 Enter" className={inputCls + ' !text-xs'} />
) } export function SectionCard({ title, subtitle, children }: { title: string; subtitle?: string; children: ReactNode }) { return (

{title}

{subtitle && {subtitle}}
{children}
) } export function SourceEditor({ sources, onChange, knownSources, examplePaths, showCustom = true, }: { sources: string[] onChange: (s: string[]) => void knownSources: KnownSource[] examplePaths?: string[] showCustom?: boolean }) { const [customInput, setCustomInput] = useState('') const knownKeys = new Set(knownSources.map(k => k.key)) const customPaths = sources.filter(s => !knownKeys.has(s)) const toggleKnown = (key: string) => { if (sources.includes(key)) { onChange(sources.filter(s => s !== key)) } else { onChange([...sources, key]) } } const addCustom = () => { const v = customInput.trim() if (v && !sources.includes(v)) { onChange([...sources, v]) setCustomInput('') } } const removeCustom = (path: string) => { onChange(sources.filter(s => s !== path)) } return (
{/* Known sources as toggles */}
{knownSources.map(src => (
{src.label}
{src.description}
toggleKnown(src.key)} />
))}
{/* Custom paths (only shown when showCustom is true) */} {showCustom && (
自定义路径
{customPaths.length > 0 && (
{customPaths.map((p) => ( {p} ))}
)}
setCustomInput(e.target.value)} onKeyDown={e => e.key === 'Enter' && (e.preventDefault(), addCustom())} placeholder="输入绝对路径,如 D:\my-skills" className={inputCls + ' !text-xs font-mono'} />
{examplePaths && (

示例: {examplePaths.join('、')}

)}
)}
) } export function MapEntryHeader({ name, onDelete, onRename }: { name: string; onDelete: () => void; onRename?: (n: string) => void }) { const [editing, setEditing] = useState(false) const [val, setVal] = useState(name) return (
{editing ? ( setVal(e.target.value)} onBlur={() => { setEditing(false); onRename?.(val.trim() || name) }} onKeyDown={e => e.key === 'Enter' && (setEditing(false), onRename?.(val.trim() || name))} className={inputCls + ' !py-1 !text-xs max-w-[200px]'} autoFocus /> ) : ( onRename && setEditing(true)}>{name} )}
) }