{}} />);
+ expect(screen.getByText('com_config_enum_selected_count 1/3')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/configuration/fields/EnumSetField.tsx b/src/components/configuration/fields/EnumSetField.tsx
new file mode 100644
index 00000000..ff77f287
--- /dev/null
+++ b/src/components/configuration/fields/EnumSetField.tsx
@@ -0,0 +1,52 @@
+import { Checkbox } from '@clickhouse/click-ui';
+import type * as t from '@/types';
+import { useLocalize } from '@/hooks';
+
+export function EnumSetField({
+ id,
+ value,
+ options,
+ onChange,
+ defaultValue,
+ disabled,
+ 'aria-label': ariaLabel,
+}: t.EnumSetFieldProps) {
+ const localize = useLocalize();
+ const checked = new Set(value ?? defaultValue ?? []);
+ const selectedCount = options.reduce((n, opt) => n + (checked.has(opt.value) ? 1 : 0), 0);
+
+ const handleToggle = (optionValue: string) => {
+ const next = new Set(checked);
+ if (next.has(optionValue)) {
+ next.delete(optionValue);
+ } else {
+ next.add(optionValue);
+ }
+ onChange(options.filter((opt) => next.has(opt.value)).map((opt) => opt.value));
+ };
+
+ return (
+
+
+ {localize('com_config_enum_selected_count', {
+ selected: selectedCount,
+ total: options.length,
+ })}
+
+ {options.map((opt) => (
+ handleToggle(opt.value)}
+ />
+ ))}
+
+ );
+}
diff --git a/src/components/configuration/fields/ListField.tsx b/src/components/configuration/fields/ListField.tsx
index 2c6edf3e..4911e308 100644
--- a/src/components/configuration/fields/ListField.tsx
+++ b/src/components/configuration/fields/ListField.tsx
@@ -11,7 +11,6 @@ export function ListField({
placeholder,
itemLabel,
variant = 'inline-edit',
- options,
'aria-label': ariaLabel,
}: t.ListFieldProps) {
const localize = useLocalize();
@@ -32,13 +31,7 @@ export function ListField({
const resolvedItemLabel = itemLabel ?? localize('com_ui_item');
const handleAdd = () => {
- if (options) {
- const used = new Set(values);
- const next = options.find((o) => !used.has(o.value))?.value ?? options[0]?.value ?? '';
- onChange([...values, next]);
- } else {
- onChange([...values, '']);
- }
+ onChange([...values, '']);
focusLastRef.current = true;
};
const handleRemove = (index: number) => onChange(values.filter((_, i) => i !== index));
@@ -60,31 +53,7 @@ export function ListField({
const itemLabel = `${resolvedItemLabel} ${index + 1}`;
let control: React.ReactNode;
- if (options) {
- if (disabled) {
- const matchedLabel = options.find((o) => o.value === value)?.label ?? value;
- control = (
-
- {matchedLabel}
-
- );
- } else {
- control = (
-
- );
- }
- } else if (variant === 'inline-edit') {
+ if (variant === 'inline-edit') {
control = (
{
expect(missing, `Missing locale keys:\n ${missing.join('\n ')}`).toHaveLength(0);
});
- it('every array field has a com_config_field_*_item locale key', () => {
- const arrays = allFields.filter((f) => f.isArray);
+ it('every array field rendered as a list has a com_config_field_*_item locale key', () => {
+ const arrays = allFields.filter(
+ (f) => f.isArray && !getArrayItemType(f.type).startsWith('enum('),
+ );
const missing: string[] = [];
for (const field of arrays) {
diff --git a/src/components/configuration/sections/__tests__/EndpointsRenderer.test.tsx b/src/components/configuration/sections/__tests__/EndpointsRenderer.test.tsx
index 76d63adf..243cb533 100644
--- a/src/components/configuration/sections/__tests__/EndpointsRenderer.test.tsx
+++ b/src/components/configuration/sections/__tests__/EndpointsRenderer.test.tsx
@@ -16,9 +16,24 @@ interface MockTextFieldProps {
disabled?: boolean;
'aria-label'?: string;
}
+interface MockCheckboxProps {
+ label?: React.ReactNode;
+ checked?: boolean;
+ disabled?: boolean;
+ onCheckedChange?: (checked: boolean) => void;
+}
vi.mock('@clickhouse/click-ui', () => ({
Icon: () => ,
+ Checkbox: ({ label, checked, disabled, onCheckedChange }: MockCheckboxProps) => (
+ onCheckedChange?.(!(checked ?? false))}
+ />
+ ),
MultiAccordion: Object.assign(
({ children }: { children: React.ReactNode }) => {children}
,
{
diff --git a/src/components/configuration/sections/__tests__/McpServersRenderer.test.tsx b/src/components/configuration/sections/__tests__/McpServersRenderer.test.tsx
index 4fd36638..656d8471 100644
--- a/src/components/configuration/sections/__tests__/McpServersRenderer.test.tsx
+++ b/src/components/configuration/sections/__tests__/McpServersRenderer.test.tsx
@@ -61,9 +61,24 @@ interface IconButtonProps {
onClick?: () => void;
'aria-label'?: string;
}
+interface CheckboxProps {
+ label?: React.ReactNode;
+ checked?: boolean;
+ disabled?: boolean;
+ onCheckedChange?: (checked: boolean) => void;
+}
vi.mock('@clickhouse/click-ui', () => ({
Icon: ({ name }: IconProps) => ,
+ Checkbox: ({ label, checked, disabled, onCheckedChange }: CheckboxProps) => (
+ onCheckedChange?.(!(checked ?? false))}
+ />
+ ),
Switch: (props: SwitchProps) => (