Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/components/configuration/ConfigRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
)}
Expand Down
49 changes: 47 additions & 2 deletions src/components/configuration/FieldRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ function ArrayObjectNestedGroup({
totalCount={items.length}
depth={field.depth}
onAdd={handleAdd}
disabled={disabled}
>
{arrayField}
</NestedGroup>
Expand Down Expand Up @@ -146,6 +147,7 @@ function RecordObjectNestedGroup({
totalCount={entries.length}
depth={field.depth}
onAdd={handleAdd}
disabled={disabled}
>
{recordField}
</NestedGroup>
Expand Down Expand Up @@ -549,7 +551,10 @@ function BooleanChip({ value }: { value: boolean }) {
const localize = useLocalize();
return (
<span
className={cn('boolean-chip', value ? 'boolean-chip-true' : 'boolean-chip-false')}
className={cn(
'boolean-chip self-start',
value ? 'boolean-chip-true' : 'boolean-chip-false',
)}
aria-label={localize(value ? 'com_ui_true' : 'com_ui_false')}
>
{localize(value ? 'com_ui_true' : 'com_ui_false')}
Expand All @@ -565,6 +570,7 @@ export function NestedGroup({
depth = 0,
onAdd,
addLabel,
disabled,
children,
}: {
label: string;
Expand All @@ -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();
Expand All @@ -585,6 +597,36 @@ export function NestedGroup({
{ defaultExpanded: hasConfigured, onAdd },
);

if (disabled) {
return (
<section
ref={sectionRef}
id={sectionId}
aria-label={label}
className={cn(depth > 0 ? 'mt-3' : 'mt-4', 'flex flex-col')}
style={indent ? { paddingLeft: indent } : undefined}
>
<div
data-section-id={sectionId}
className="flex items-center gap-2 border-b border-(--cui-color-stroke-default) py-2 pl-1"
>
<span className="text-sm font-medium text-(--cui-color-text-default)">{label}</span>
{totalCount > 0 && (
<span
className={cn(
'config-count-badge',
hasConfigured ? 'config-count-badge-active' : 'config-count-badge-muted',
)}
>
{configuredCount}/{totalCount}
</span>
)}
</div>
{children}
</section>
);
}

return (
<section
ref={sectionRef}
Expand Down Expand Up @@ -852,8 +894,9 @@ function NestedGroupWithAddField({
return (
<NestedGroup
label={label}
onAdd={hasHideable ? () => addFieldRef.current?.() : undefined}
onAdd={!disabled && hasHideable ? () => addFieldRef.current?.() : undefined}
addLabel={localize('com_config_add_field')}
disabled={disabled}
>
<InlineFieldRenderer
fields={fields}
Expand Down Expand Up @@ -960,6 +1003,7 @@ export function renderInlineField(
id={fieldId}
checked={Boolean(fieldValue)}
onChange={(checked) => onChange(field.key, checked)}
disabled={disabled}
aria-label={fieldLabel}
/>
</InlineRow>
Expand Down Expand Up @@ -1217,6 +1261,7 @@ export function FieldRenderer({
configuredCount={nestedCounts.configured}
totalCount={nestedCounts.total}
depth={group.field.depth}
disabled={disabled}
>
<FieldRenderer
fields={group.field.children!}
Expand Down
38 changes: 23 additions & 15 deletions src/components/configuration/fields/ListField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,29 @@ export function ListField({

let control: React.ReactNode;
if (options) {
control = (
<select
value={value}
onChange={(e) => handleChange(index, e.target.value)}
disabled={disabled}
aria-label={itemLabel}
className="config-input flex-1"
>
{options.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
);
if (disabled) {
const matchedLabel = options.find((o) => o.value === value)?.label ?? value;
control = (
<span className="config-input flex-1" aria-label={itemLabel}>
{matchedLabel}
</span>
);
} else {
control = (
<select
value={value}
onChange={(e) => handleChange(index, e.target.value)}
aria-label={itemLabel}
className="config-input flex-1"
>
{options.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
);
}
} else if (variant === 'inline-edit') {
control = (
<input
Expand Down
52 changes: 31 additions & 21 deletions src/components/configuration/sections/EndpointsRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,7 @@ function ProviderSection({
totalCount={restChildren.length}
configuredCount={restConfigured}
depth={2}
disabled={rendererProps.disabled}
>
<FieldRenderer fields={restChildren} {...rendererProps} />
</NestedGroup>
Expand Down Expand Up @@ -614,30 +615,39 @@ export function CustomEndpointsRenderer(props: t.FieldRendererProps) {
onChange(path, [...items, entry]);
};

const isEmpty = items.length === 0;

return (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-3 py-2">
<button
type="button"
onClick={() => setCreateOpen(true)}
{!disabled && (
<div className="flex items-center gap-3 py-2">
<button
type="button"
onClick={() => setCreateOpen(true)}
className="config-add-btn"
>
<Icon name="plus" size="sm" />
<span>{localize('com_config_create_endpoint')}</span>
</button>
</div>
)}
{disabled && isEmpty ? (
<div className="py-3 text-sm text-(--cui-color-text-muted)">
{localize('com_config_no_custom_endpoints')}
</div>
) : (
<ArrayObjectField
id={`${path.replace(/\./g, '-')}`}
value={value}
fields={customField.children ?? []}
onChange={(v) => onChange(path, v)}
onEntryChange={(index, v) => onChange(`${path}.${index}`, v)}
disabled={disabled}
className="config-add-btn"
>
<Icon name="plus" size="sm" />
<span>{localize('com_config_create_endpoint')}</span>
</button>
</div>
<ArrayObjectField
id={`${path.replace(/\./g, '-')}`}
value={value}
fields={customField.children ?? []}
onChange={(v) => 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`}
/>
)}
<CreateCustomEndpointDialog
open={createOpen}
onClose={() => setCreateOpen(false)}
Expand Down
32 changes: 20 additions & 12 deletions src/components/configuration/sections/McpServersRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -906,19 +906,27 @@ export function McpServersRenderer(props: t.FieldRendererProps) {
[onChange, path],
);

const isEmpty = entries.length === 0;

return (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-3 py-2">
<button
type="button"
onClick={() => setCreateOpen(true)}
disabled={disabled}
className="config-add-btn"
>
<Icon name="plus" size="sm" />
<span>{localize('com_config_create_mcp_server')}</span>
</button>
</div>
{!disabled && (
<div className="flex items-center gap-3 py-2">
<button
type="button"
onClick={() => setCreateOpen(true)}
className="config-add-btn"
>
<Icon name="plus" size="sm" />
<span>{localize('com_config_create_mcp_server')}</span>
</button>
</div>
)}
{disabled && isEmpty && (
<div className="py-3 text-sm text-(--cui-color-text-muted)">
{localize('com_config_no_mcp_servers')}
</div>
Comment thread
dustinhealy marked this conversation as resolved.
)}
{entries.map(([key, entryValue]) => (
<McpEntryRow
key={key}
Expand All @@ -935,7 +943,7 @@ export function McpServersRenderer(props: t.FieldRendererProps) {
justAdded={key === justAddedKey}
/>
))}
{entries.length === 0 && (
{!disabled && entries.length === 0 && (
<p className="py-2 text-sm text-(--cui-color-text-muted)">
{localize('com_config_no_entries')}
</p>
Expand Down
2 changes: 2 additions & 0 deletions src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down