diff --git a/src/components/configuration/ConfigRow.tsx b/src/components/configuration/ConfigRow.tsx index c3a0b16..1e453d6 100644 --- a/src/components/configuration/ConfigRow.tsx +++ b/src/components/configuration/ConfigRow.tsx @@ -80,7 +80,6 @@ export function ConfigRow({ className={cn( 'config-row flex w-full gap-6 rounded-md px-2.5 py-2 transition-opacity', hasSubContent ? 'items-start' : 'items-center', - disabled && 'pointer-events-none', isPendingReset && 'opacity-50', !isPendingReset && !isConfigured && !isTouched && 'opacity-50', )} diff --git a/src/components/configuration/FieldRenderer.tsx b/src/components/configuration/FieldRenderer.tsx index 0a33409..fec710b 100644 --- a/src/components/configuration/FieldRenderer.tsx +++ b/src/components/configuration/FieldRenderer.tsx @@ -93,6 +93,7 @@ function ArrayObjectNestedGroup({ totalCount={items.length} depth={field.depth} onAdd={handleAdd} + disabled={disabled} > {arrayField} @@ -146,6 +147,7 @@ function RecordObjectNestedGroup({ totalCount={entries.length} depth={field.depth} onAdd={handleAdd} + disabled={disabled} > {recordField} @@ -549,7 +551,10 @@ function BooleanChip({ value }: { value: boolean }) { const localize = useLocalize(); return ( {localize(value ? 'com_ui_true' : 'com_ui_false')} @@ -565,6 +570,7 @@ export function NestedGroup({ depth = 0, onAdd, addLabel, + disabled, children, }: { label: string; @@ -575,6 +581,12 @@ export function NestedGroup({ /** When provided, renders a "+ Add" button inline in the header. */ onAdd?: () => void; addLabel?: string; + /** + * When true, the caret/collapse affordance is dropped and the group renders + * flat with a static heading. Used in fully read-only sections where the + * expand/collapse interaction would be misleading. + */ + disabled?: boolean; children: ReactNode; }) { const localize = useLocalize(); @@ -585,6 +597,36 @@ export function NestedGroup({ { defaultExpanded: hasConfigured, onAdd }, ); + if (disabled) { + return ( +
0 ? 'mt-3' : 'mt-4', 'flex flex-col')} + style={indent ? { paddingLeft: indent } : undefined} + > +
+ {label} + {totalCount > 0 && ( + + {configuredCount}/{totalCount} + + )} +
+ {children} +
+ ); + } + return (
addFieldRef.current?.() : undefined} + onAdd={!disabled && hasHideable ? () => addFieldRef.current?.() : undefined} addLabel={localize('com_config_add_field')} + disabled={disabled} > onChange(field.key, checked)} + disabled={disabled} aria-label={fieldLabel} /> @@ -1217,6 +1261,7 @@ export function FieldRenderer({ configuredCount={nestedCounts.configured} totalCount={nestedCounts.total} depth={group.field.depth} + disabled={disabled} > handleChange(index, e.target.value)} - disabled={disabled} - aria-label={itemLabel} - className="config-input flex-1" - > - {options.map((opt) => ( - - ))} - - ); + if (disabled) { + const matchedLabel = options.find((o) => o.value === value)?.label ?? value; + control = ( + + {matchedLabel} + + ); + } else { + control = ( + + ); + } } else if (variant === 'inline-edit') { control = ( @@ -614,30 +615,39 @@ export function CustomEndpointsRenderer(props: t.FieldRendererProps) { onChange(path, [...items, entry]); }; + const isEmpty = items.length === 0; + return (
-
- +
+ )} + {disabled && isEmpty ? ( +
+ {localize('com_config_no_custom_endpoints')} +
+ ) : ( + onChange(path, v)} + onEntryChange={(index, v) => onChange(`${path}.${index}`, v)} disabled={disabled} - className="config-add-btn" - > - - {localize('com_config_create_endpoint')} - -
- onChange(path, v)} - onEntryChange={(index, v) => onChange(`${path}.${index}`, v)} - disabled={disabled} - hideAddButton - renderFields={renderGroupedEndpointFields} - entryIdPrefix={`section-${path.split('.')[0]}-custom`} - /> + hideAddButton + renderFields={renderGroupedEndpointFields} + entryIdPrefix={`section-${path.split('.')[0]}-custom`} + /> + )} setCreateOpen(false)} diff --git a/src/components/configuration/sections/McpServersRenderer.tsx b/src/components/configuration/sections/McpServersRenderer.tsx index 8b577fe..34f5d5c 100644 --- a/src/components/configuration/sections/McpServersRenderer.tsx +++ b/src/components/configuration/sections/McpServersRenderer.tsx @@ -906,19 +906,27 @@ export function McpServersRenderer(props: t.FieldRendererProps) { [onChange, path], ); + const isEmpty = entries.length === 0; + return (
-
- -
+ {!disabled && ( +
+ +
+ )} + {disabled && isEmpty && ( +
+ {localize('com_config_no_mcp_servers')} +
+ )} {entries.map(([key, entryValue]) => ( ))} - {entries.length === 0 && ( + {!disabled && entries.length === 0 && (

{localize('com_config_no_entries')}

diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 3f95eaa..66ecbcc 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -70,6 +70,8 @@ "com_config_select_field": "Select a field...", "com_config_more_settings": "More settings", "com_config_create_endpoint": "Create endpoint", + "com_config_no_custom_endpoints": "No custom endpoints configured", + "com_config_no_mcp_servers": "No MCP servers configured", "com_config_endpoint_name_required": "Endpoint name is required", "com_config_create_mcp_server": "Create MCP server", "com_config_server_name": "Server name",