Files
jiang13-bbs/frontend/components/admin/AdminField.tsx
freefire 40bbad2c82 公开页补齐自引用 Canonical 与按类型的结构化数据,图片补上说明文字。
后台设置和内容编辑改成可导航布局;部署用访客 origin 生成 Canonical,避免落到容器地址。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-20 00:11:24 +08:00

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>
);
}