// Generated by scripts/sync-shared.mjs from shared/client/settings/PluginSettingsCard.tsx. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs". /** * Family-shared chrome for plugin settings cards: a disclosure header naming * the plugin and what its settings govern, the controls inside, and the save * that writes them. Renders nothing while the namespace is unavailable — a * deployment that does not compose the owning plugin should show no trace of * it. Inlined into each consumer's client bundle; mirrors the official * ui-plugin-config PluginCard in a self-contained slice. */ import { useState, type ReactNode } from 'react' import type { CardShell } from './settings-form.ts' import css from './settings-card.module.css' /** Copy keys the card chrome itself reads; every consumer locale carries this shared vocabulary. */ export const CARD_COPY_KEYS = [ 'settings.collapse', 'settings.expand', 'settings.notExposed', 'settings.unsaved', 'settings.readOnly', 'settings.saveFailed', 'settings.discard', 'settings.save', 'settings.saving', ] as const /** Copy key the card chrome itself reads. */ export type CardCopyKey = (typeof CARD_COPY_KEYS)[number] /** Key domain for the plugin's own copy (inferred per consumer). */ export type SettingsCardKey = TKey | CardCopyKey /** Card chrome shared by every plugin settings card. */ export interface PluginSettingsCardProps { /** Locale reader for this card's copy. */ t: (key: SettingsCardKey, params?: Record) => string /** Locale key of the plugin's name. */ titleKey: TKey /** Locale key of the line describing what this plugin's settings govern. */ descriptionKey: TKey /** The card's form state: availability, writability, and what a save would do. */ state: CardShell /** Write every staged edit. */ onSave: () => void /** Drop every staged edit. */ onDiscard: () => void /** * Render the controls expanded on arrival (still collapsible). Defaults to * true: promoted first-level sections show their settings immediately. */ defaultOpen?: boolean /** * Render without the collapse affordance: a static header and an always * visible body. Used by first-level settings sections whose nav entry * already provides the selection. */ alwaysOpen?: boolean /** The plugin's controls. */ children: ReactNode } /** * Render one plugin settings card. * @param props - the plugin's copy keys, its form state, and its controls. * @returns the card, or nothing while the namespace is still loading. */ export function PluginSettingsCard(props: PluginSettingsCardProps) { const [open, setOpen] = useState(props.defaultOpen ?? true) const { state, alwaysOpen } = props if (!state.available) return null const title = props.t(props.titleKey) const description = props.t(props.descriptionKey) const blocked = !state.dirty || state.invalid || state.saving const expanded = alwaysOpen === true || open const cardClass = expanded ? `${css.cardOpen} ${css.card}` : css.card // With alwaysOpen the nav entry already provides the selection, so the // header is a static title row instead of a disclosure button. const header = alwaysOpen === true ? (
{title} {description} {state.dirty ? {props.t('settings.unsaved')} : null}
) : ( ) // The namespace exists but the Host does not serve it to this client (the // official settings allowlist omits third-party namespaces): show a card // that explains the gap instead of vanishing, so a missing card never // reads as a missing plugin. if (!state.exposed) { return (
  • {header} {expanded ? (

    {props.t('settings.notExposed')}

    ) : null}
  • ) } return (
  • {header} {expanded ? (
    {!state.writable ?

    {props.t('settings.readOnly')}

    : null} {props.children}
    {state.failed ? (

    {props.t('settings.saveFailed')}{state.failedReason ? ' - ' + state.failedReason : ''}

    ) : null}
    ) : null}
  • ) } /** Props every field control needs regardless of its value type. */ export interface FieldProps { /** Stable id associating the label with its control. */ id: string /** Visible label. */ label: string /** One-line explanation rendered under the control. */ hint: string /** Draft text this control renders. */ text: string /** True when saving would leave a user-layer entry for this field. */ overridden: boolean /** True when the draft is not a value this field accepts. */ invalid: boolean /** Copy for the overridden badge. */ overriddenLabel: string /** Copy for the reset control. */ resetLabel: string /** Copy shown in place of the hint while the draft is invalid. */ invalidLabel: string /** Disables every control (read-only document, or an unavailable namespace). */ disabled: boolean /** Stage draft text. */ onEdit: (text: string) => void /** Stage a clear so the field re-inherits the composition layer. */ onReset: () => void } /** A staged value field. `numeric` only hints the keypad: which drafts a field accepts is decided by its spec. */ export function ValueField(props: FieldProps & { /** Hints a numeric keypad without narrowing what the control accepts. */ numeric?: boolean /** Placeholder shown while the draft is empty. */ placeholder?: string }) { return (
    {props.overridden ? ( {props.overriddenLabel} ) : null}
    { props.onEdit(event.target.value) }} />

    {props.invalid ? props.invalidLabel : props.hint}

    ) } /** A staged boolean field: 继承 / 开 / 关. */ export function BooleanField(props: FieldProps & { /** Copy for the inherit option. */ inheritLabel: string /** Copy for the on option. */ onLabel: string /** Copy for the off option. */ offLabel: string }) { return (
    {props.overridden ? ( {props.overriddenLabel} ) : null}

    {props.hint}

    ) } /** A staged enumerated field rendered as a select. */ export function ChoiceField(props: FieldProps & { /** Copy for the inherit option (draft text is the empty string). */ inheritLabel: string /** Choices rendered in order; `value` is the draft/stored text. */ choices: ReadonlyArray<{ value: string; label: string }> }) { return (
    {props.overridden ? ( {props.overriddenLabel} ) : null}

    {props.invalid ? props.invalidLabel : props.hint}

    ) }