后台设置和内容编辑改成可导航布局;部署用访客 origin 生成 Canonical,避免落到容器地址。 Co-authored-by: Cursor <cursoragent@cursor.com>
108 lines
2.9 KiB
TypeScript
108 lines
2.9 KiB
TypeScript
import type { ChangeEvent, ReactNode } from "react";
|
|
|
|
/** 后台文本框:与聊天搜索同款壳层,聚焦只画外框一圈 */
|
|
export default function AdminField({
|
|
id,
|
|
label,
|
|
hint,
|
|
value,
|
|
onChange,
|
|
placeholder,
|
|
maxLength,
|
|
as = "input",
|
|
type = "text",
|
|
autoComplete,
|
|
disabled,
|
|
className = "",
|
|
minHeight,
|
|
embed,
|
|
error,
|
|
describedBy,
|
|
}: {
|
|
id: string;
|
|
label: ReactNode;
|
|
hint?: ReactNode;
|
|
value: string;
|
|
onChange: (v: string) => void;
|
|
placeholder?: string;
|
|
maxLength?: number;
|
|
as?: "input" | "textarea";
|
|
type?: string;
|
|
autoComplete?: string;
|
|
disabled?: boolean;
|
|
className?: string;
|
|
minHeight?: string;
|
|
/** 标签由外层设置行提供,本组件只保留计数与输入 */
|
|
embed?: boolean;
|
|
error?: string;
|
|
describedBy?: string;
|
|
}) {
|
|
const handle = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
|
|
onChange(e.target.value);
|
|
|
|
const invalid = Boolean(error);
|
|
const described = [describedBy, error ? `${id}-error` : null].filter(Boolean).join(" ") || undefined;
|
|
|
|
const count =
|
|
maxLength != null ? (
|
|
<span className={`admin-field-count${embed ? " is-infield" : ""}`} aria-hidden>
|
|
{value.length}/{maxLength}
|
|
</span>
|
|
) : null;
|
|
|
|
return (
|
|
<div className={className}>
|
|
{embed ? null : (
|
|
<div className="admin-field-head">
|
|
<label className="field-label" htmlFor={id}>
|
|
{label}
|
|
</label>
|
|
{count}
|
|
</div>
|
|
)}
|
|
{!embed && hint != null && hint !== "" && (
|
|
<p className="meta text-[12.5px] mb-2.5 leading-relaxed">{hint}</p>
|
|
)}
|
|
{as === "textarea" ? (
|
|
<div className={`admin-field-shell is-area${embed && count ? " has-count" : ""}${invalid ? " is-invalid" : ""}`}>
|
|
{embed ? count : null}
|
|
<textarea
|
|
id={id}
|
|
className="admin-field-input"
|
|
style={minHeight ? { minHeight } : undefined}
|
|
maxLength={maxLength}
|
|
value={value}
|
|
onChange={handle}
|
|
placeholder={placeholder}
|
|
disabled={disabled}
|
|
aria-invalid={invalid || undefined}
|
|
aria-describedby={described}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div className={`admin-field-shell${embed && count ? " has-count" : ""}${invalid ? " is-invalid" : ""}`}>
|
|
<input
|
|
id={id}
|
|
type={type}
|
|
className="admin-field-input"
|
|
maxLength={maxLength}
|
|
value={value}
|
|
onChange={handle}
|
|
placeholder={placeholder}
|
|
autoComplete={autoComplete}
|
|
disabled={disabled}
|
|
aria-invalid={invalid || undefined}
|
|
aria-describedby={described}
|
|
/>
|
|
{embed ? count : null}
|
|
</div>
|
|
)}
|
|
{error ? (
|
|
<p className="admin-settings-row-error" id={`${id}-error`} role="alert">
|
|
{error}
|
|
</p>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|