@camstack/ui-library 1.2.277 → 1.2.279
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/composites/config-form-builder.d.ts +11 -0
- package/dist/index.cjs +87 -60
- package/dist/index.js +87 -60
- package/package.json +1 -1
|
@@ -20,5 +20,16 @@ interface FormBuilderProps {
|
|
|
20
20
|
*/
|
|
21
21
|
bare?: boolean;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Container-query driven responsive grid. Each section is its own
|
|
25
|
+
* `@container` so the column count adapts to the SECTION's width
|
|
26
|
+
* (not the viewport) — important inside narrow hosts like the
|
|
27
|
+
* device-detail right column / ItemFormDialog. Behavior:
|
|
28
|
+
* columns=1 → always 1 col
|
|
29
|
+
* columns=2 (default) → 1 col below 480px container, 2 cols above
|
|
30
|
+
* columns=3 → 1 → 2 → 3 col at 480px / 880px
|
|
31
|
+
* columns=4 → 1 → 2 → 4 col at 480px / 1140px
|
|
32
|
+
*/
|
|
33
|
+
export declare function sectionGridClass(columns: 1 | 2 | 3 | 4 | undefined): string;
|
|
23
34
|
export declare function ConfigFormBuilder({ schema, values, onChange, disabled, translationFn, onTestField, probeResults, onClearProbe, bare }: FormBuilderProps): JSX.Element;
|
|
24
35
|
export {};
|
package/dist/index.cjs
CHANGED
|
@@ -10957,6 +10957,43 @@ var PasswordInput = (0, react$1.forwardRef)(({ value, onValueChange, shown, onTo
|
|
|
10957
10957
|
});
|
|
10958
10958
|
PasswordInput.displayName = "PasswordInput";
|
|
10959
10959
|
//#endregion
|
|
10960
|
+
//#region src/primitives/select.tsx
|
|
10961
|
+
/**
|
|
10962
|
+
* A native `<select>` styled to match the rest of the field set.
|
|
10963
|
+
*
|
|
10964
|
+
* `appearance-none` plus an explicit `bg-surface` / `text-foreground` is what
|
|
10965
|
+
* makes it match — and it is also what USED to make a disabled one a lie. A
|
|
10966
|
+
* browser greys a disabled control by rendering its own background and text
|
|
10967
|
+
* colour; all three of those are overridden here, so a `disabled` dropdown
|
|
10968
|
+
* looked exactly like a live one and simply ignored the click. That is the
|
|
10969
|
+
* whole of the "the language selector does not open" report of 2026-08-15:
|
|
10970
|
+
* the AI section's switch was off, every control under it was correctly inert,
|
|
10971
|
+
* and nothing on screen said so.
|
|
10972
|
+
*
|
|
10973
|
+
* `disabled:` classes belong HERE and not on each call site: there are dozens
|
|
10974
|
+
* of `<Select disabled>` in this app and the one that gets forgotten is the one
|
|
10975
|
+
* that generates the ticket. Guarded by `disabled-affordance.test.tsx`.
|
|
10976
|
+
*/
|
|
10977
|
+
var Select = (0, react$1.forwardRef)(({ className, options, disabled, ...props }, ref) => {
|
|
10978
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10979
|
+
className: "relative w-full",
|
|
10980
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
10981
|
+
ref,
|
|
10982
|
+
disabled,
|
|
10983
|
+
className: cn("h-7 w-full px-2.5 text-xs bg-surface border border-border rounded-md text-foreground appearance-none pr-7 focus:outline-none focus:ring-1 focus:ring-primary", "disabled:opacity-50 disabled:cursor-not-allowed", className),
|
|
10984
|
+
...props,
|
|
10985
|
+
children: options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
10986
|
+
value: option.value,
|
|
10987
|
+
children: option.label
|
|
10988
|
+
}, option.value))
|
|
10989
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10990
|
+
className: cn("absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none text-foreground-muted", disabled === true ? "opacity-50" : ""),
|
|
10991
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: "h-3.5 w-3.5" })
|
|
10992
|
+
})]
|
|
10993
|
+
});
|
|
10994
|
+
});
|
|
10995
|
+
Select.displayName = "Select";
|
|
10996
|
+
//#endregion
|
|
10960
10997
|
//#region src/composites/config-secret-field.tsx
|
|
10961
10998
|
/**
|
|
10962
10999
|
* A keychain-stored secret in a settings form (D787, D789) — drawn as ONE
|
|
@@ -21313,43 +21350,6 @@ var Button = (0, react$1.forwardRef)(({ className, variant, size, ...props }, re
|
|
|
21313
21350
|
});
|
|
21314
21351
|
Button.displayName = "Button";
|
|
21315
21352
|
//#endregion
|
|
21316
|
-
//#region src/primitives/select.tsx
|
|
21317
|
-
/**
|
|
21318
|
-
* A native `<select>` styled to match the rest of the field set.
|
|
21319
|
-
*
|
|
21320
|
-
* `appearance-none` plus an explicit `bg-surface` / `text-foreground` is what
|
|
21321
|
-
* makes it match — and it is also what USED to make a disabled one a lie. A
|
|
21322
|
-
* browser greys a disabled control by rendering its own background and text
|
|
21323
|
-
* colour; all three of those are overridden here, so a `disabled` dropdown
|
|
21324
|
-
* looked exactly like a live one and simply ignored the click. That is the
|
|
21325
|
-
* whole of the "the language selector does not open" report of 2026-08-15:
|
|
21326
|
-
* the AI section's switch was off, every control under it was correctly inert,
|
|
21327
|
-
* and nothing on screen said so.
|
|
21328
|
-
*
|
|
21329
|
-
* `disabled:` classes belong HERE and not on each call site: there are dozens
|
|
21330
|
-
* of `<Select disabled>` in this app and the one that gets forgotten is the one
|
|
21331
|
-
* that generates the ticket. Guarded by `disabled-affordance.test.tsx`.
|
|
21332
|
-
*/
|
|
21333
|
-
var Select = (0, react$1.forwardRef)(({ className, options, disabled, ...props }, ref) => {
|
|
21334
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
21335
|
-
className: "relative w-full",
|
|
21336
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
21337
|
-
ref,
|
|
21338
|
-
disabled,
|
|
21339
|
-
className: cn("h-7 w-full px-2.5 text-xs bg-surface border border-border rounded-md text-foreground appearance-none pr-7 focus:outline-none focus:ring-1 focus:ring-primary", "disabled:opacity-50 disabled:cursor-not-allowed", className),
|
|
21340
|
-
...props,
|
|
21341
|
-
children: options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
21342
|
-
value: option.value,
|
|
21343
|
-
children: option.label
|
|
21344
|
-
}, option.value))
|
|
21345
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21346
|
-
className: cn("absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none text-foreground-muted", disabled === true ? "opacity-50" : ""),
|
|
21347
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: "h-3.5 w-3.5" })
|
|
21348
|
-
})]
|
|
21349
|
-
});
|
|
21350
|
-
});
|
|
21351
|
-
Select.displayName = "Select";
|
|
21352
|
-
//#endregion
|
|
21353
21353
|
//#region src/primitives/skeleton.tsx
|
|
21354
21354
|
var Skeleton = (0, react$1.forwardRef)(({ className, ...props }, ref) => {
|
|
21355
21355
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -28322,7 +28322,7 @@ function TimezoneSelector({ value, onChange, disabled }) {
|
|
|
28322
28322
|
var INPUT_CLASS = "w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-xs text-foreground focus:border-primary focus:ring-1 focus:ring-primary/30 outline-none disabled:opacity-50 disabled:cursor-not-allowed";
|
|
28323
28323
|
var LABEL_CLASS$1 = "block text-[11px] font-medium text-foreground mb-1 break-words";
|
|
28324
28324
|
var DESC_CLASS = "text-[10px] text-foreground-subtle mt-0.5 break-words";
|
|
28325
|
-
function FieldWrapper({ label, description, required, span, children, translationFn }) {
|
|
28325
|
+
function FieldWrapper({ label, description, required, span, htmlFor, children, translationFn }) {
|
|
28326
28326
|
const colSpanClass = span === 2 ? "col-span-1 @[480px]:col-span-2" : span === 3 ? "col-span-1 @[480px]:col-span-2 @[880px]:col-span-3" : span === 4 ? "col-span-1 @[480px]:col-span-2 @[1140px]:col-span-4" : "col-span-1";
|
|
28327
28327
|
const resolvedLabel = resolveLabel(label, translationFn);
|
|
28328
28328
|
const resolvedDescription = resolveLabel(description, translationFn);
|
|
@@ -28332,6 +28332,7 @@ function FieldWrapper({ label, description, required, span, children, translatio
|
|
|
28332
28332
|
children: [
|
|
28333
28333
|
hasLabel && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
28334
28334
|
className: LABEL_CLASS$1,
|
|
28335
|
+
...htmlFor !== void 0 ? { htmlFor } : {},
|
|
28335
28336
|
children: [resolvedLabel, required && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
28336
28337
|
className: "text-danger ml-0.5",
|
|
28337
28338
|
children: "*"
|
|
@@ -28500,24 +28501,27 @@ function BooleanField({ field, value, onChange, disabled, translationFn }) {
|
|
|
28500
28501
|
}
|
|
28501
28502
|
function SelectField({ field, value, onChange, disabled, translationFn }) {
|
|
28502
28503
|
const hasEmptyOption = field.options.some((o) => o.value === "");
|
|
28504
|
+
const options = [...!field.required && !hasEmptyOption ? [{
|
|
28505
|
+
value: "",
|
|
28506
|
+
label: "— Select —"
|
|
28507
|
+
}] : [], ...field.options.map((opt) => ({
|
|
28508
|
+
value: opt.value,
|
|
28509
|
+
label: opt.label
|
|
28510
|
+
}))];
|
|
28511
|
+
const controlId = `config-select-${field.key}`;
|
|
28503
28512
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldWrapper, {
|
|
28504
28513
|
label: field.label,
|
|
28505
28514
|
description: field.description,
|
|
28506
28515
|
required: field.required,
|
|
28507
28516
|
span: field.span,
|
|
28517
|
+
htmlFor: controlId,
|
|
28508
28518
|
translationFn,
|
|
28509
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
28510
|
-
|
|
28519
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Select, {
|
|
28520
|
+
id: controlId,
|
|
28521
|
+
options,
|
|
28511
28522
|
value: value === void 0 || value === null ? "" : String(value),
|
|
28512
28523
|
disabled: disabled || field.disabled,
|
|
28513
|
-
onChange: (e) => onChange(e.target.value)
|
|
28514
|
-
children: [!field.required && !hasEmptyOption && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
28515
|
-
value: "",
|
|
28516
|
-
children: "— Select —"
|
|
28517
|
-
}), field.options.map((opt) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
28518
|
-
value: opt.value,
|
|
28519
|
-
children: opt.label
|
|
28520
|
-
}, opt.value))]
|
|
28524
|
+
onChange: (e) => onChange(e.target.value)
|
|
28521
28525
|
})
|
|
28522
28526
|
});
|
|
28523
28527
|
}
|
|
@@ -30125,11 +30129,27 @@ function resolveLabel(value, t) {
|
|
|
30125
30129
|
if (t && isI18nKey(value)) return t(value);
|
|
30126
30130
|
return value;
|
|
30127
30131
|
}
|
|
30132
|
+
/**
|
|
30133
|
+
* Container-query driven responsive grid. Each section is its own
|
|
30134
|
+
* `@container` so the column count adapts to the SECTION's width
|
|
30135
|
+
* (not the viewport) — important inside narrow hosts like the
|
|
30136
|
+
* device-detail right column / ItemFormDialog. Behavior:
|
|
30137
|
+
* columns=1 → always 1 col
|
|
30138
|
+
* columns=2 (default) → 1 col below 480px container, 2 cols above
|
|
30139
|
+
* columns=3 → 1 → 2 → 3 col at 480px / 880px
|
|
30140
|
+
* columns=4 → 1 → 2 → 4 col at 480px / 1140px
|
|
30141
|
+
*/
|
|
30142
|
+
function sectionGridClass(columns) {
|
|
30143
|
+
const cols = columns ?? 2;
|
|
30144
|
+
if (cols === 1) return "grid-cols-1";
|
|
30145
|
+
if (cols === 3) return "grid-cols-1 @[480px]:grid-cols-2 @[880px]:grid-cols-3";
|
|
30146
|
+
if (cols === 4) return "grid-cols-1 @[480px]:grid-cols-2 @[1140px]:grid-cols-4";
|
|
30147
|
+
return "grid-cols-1 @[480px]:grid-cols-2";
|
|
30148
|
+
}
|
|
30128
30149
|
function SectionCard$1({ section, values, onChange, disabled, translationFn, onTestField, probeResults }) {
|
|
30129
30150
|
const [collapsed, setCollapsed] = (0, react$1.useState)(section.defaultCollapsed ?? false);
|
|
30130
30151
|
const isAccordion = section.style === "accordion";
|
|
30131
|
-
const
|
|
30132
|
-
const gridClass = columns === 1 ? "grid-cols-1" : columns === 3 ? "grid-cols-1 @[480px]:grid-cols-2 @[880px]:grid-cols-3" : columns === 4 ? "grid-cols-1 @[480px]:grid-cols-2 @[1140px]:grid-cols-4" : "grid-cols-1 @[480px]:grid-cols-2";
|
|
30152
|
+
const gridClass = sectionGridClass(section.columns);
|
|
30133
30153
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
30134
30154
|
className: "@container rounded-lg border border-border bg-surface p-3",
|
|
30135
30155
|
style: { containerType: "inline-size" },
|
|
@@ -30169,16 +30189,23 @@ function ConfigFormBuilder({ schema, values, onChange, disabled, translationFn,
|
|
|
30169
30189
|
onClearProbe?.(key);
|
|
30170
30190
|
};
|
|
30171
30191
|
if (bare) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
30172
|
-
className: "
|
|
30173
|
-
children: schema.sections.
|
|
30174
|
-
|
|
30175
|
-
|
|
30176
|
-
|
|
30177
|
-
|
|
30178
|
-
|
|
30179
|
-
|
|
30180
|
-
|
|
30181
|
-
|
|
30192
|
+
className: "space-y-4",
|
|
30193
|
+
children: schema.sections.map((section) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
30194
|
+
className: "@container",
|
|
30195
|
+
style: { containerType: "inline-size" },
|
|
30196
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
30197
|
+
className: `grid ${sectionGridClass(section.columns)} gap-x-3 gap-y-2.5`,
|
|
30198
|
+
children: section.fields.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormField, {
|
|
30199
|
+
field,
|
|
30200
|
+
values,
|
|
30201
|
+
onChange: handleFieldChange,
|
|
30202
|
+
disabled,
|
|
30203
|
+
translationFn,
|
|
30204
|
+
onTestField,
|
|
30205
|
+
externalProbe: probeResults?.[field.key]
|
|
30206
|
+
}, field.key))
|
|
30207
|
+
})
|
|
30208
|
+
}, section.id))
|
|
30182
30209
|
});
|
|
30183
30210
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
30184
30211
|
className: "space-y-4",
|
package/dist/index.js
CHANGED
|
@@ -10933,6 +10933,43 @@ var PasswordInput = forwardRef(({ value, onValueChange, shown, onToggleShown, bu
|
|
|
10933
10933
|
});
|
|
10934
10934
|
PasswordInput.displayName = "PasswordInput";
|
|
10935
10935
|
//#endregion
|
|
10936
|
+
//#region src/primitives/select.tsx
|
|
10937
|
+
/**
|
|
10938
|
+
* A native `<select>` styled to match the rest of the field set.
|
|
10939
|
+
*
|
|
10940
|
+
* `appearance-none` plus an explicit `bg-surface` / `text-foreground` is what
|
|
10941
|
+
* makes it match — and it is also what USED to make a disabled one a lie. A
|
|
10942
|
+
* browser greys a disabled control by rendering its own background and text
|
|
10943
|
+
* colour; all three of those are overridden here, so a `disabled` dropdown
|
|
10944
|
+
* looked exactly like a live one and simply ignored the click. That is the
|
|
10945
|
+
* whole of the "the language selector does not open" report of 2026-08-15:
|
|
10946
|
+
* the AI section's switch was off, every control under it was correctly inert,
|
|
10947
|
+
* and nothing on screen said so.
|
|
10948
|
+
*
|
|
10949
|
+
* `disabled:` classes belong HERE and not on each call site: there are dozens
|
|
10950
|
+
* of `<Select disabled>` in this app and the one that gets forgotten is the one
|
|
10951
|
+
* that generates the ticket. Guarded by `disabled-affordance.test.tsx`.
|
|
10952
|
+
*/
|
|
10953
|
+
var Select = forwardRef(({ className, options, disabled, ...props }, ref) => {
|
|
10954
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
10955
|
+
className: "relative w-full",
|
|
10956
|
+
children: [/* @__PURE__ */ jsx("select", {
|
|
10957
|
+
ref,
|
|
10958
|
+
disabled,
|
|
10959
|
+
className: cn("h-7 w-full px-2.5 text-xs bg-surface border border-border rounded-md text-foreground appearance-none pr-7 focus:outline-none focus:ring-1 focus:ring-primary", "disabled:opacity-50 disabled:cursor-not-allowed", className),
|
|
10960
|
+
...props,
|
|
10961
|
+
children: options.map((option) => /* @__PURE__ */ jsx("option", {
|
|
10962
|
+
value: option.value,
|
|
10963
|
+
children: option.label
|
|
10964
|
+
}, option.value))
|
|
10965
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
10966
|
+
className: cn("absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none text-foreground-muted", disabled === true ? "opacity-50" : ""),
|
|
10967
|
+
children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" })
|
|
10968
|
+
})]
|
|
10969
|
+
});
|
|
10970
|
+
});
|
|
10971
|
+
Select.displayName = "Select";
|
|
10972
|
+
//#endregion
|
|
10936
10973
|
//#region src/composites/config-secret-field.tsx
|
|
10937
10974
|
/**
|
|
10938
10975
|
* A keychain-stored secret in a settings form (D787, D789) — drawn as ONE
|
|
@@ -21289,43 +21326,6 @@ var Button = forwardRef(({ className, variant, size, ...props }, ref) => {
|
|
|
21289
21326
|
});
|
|
21290
21327
|
Button.displayName = "Button";
|
|
21291
21328
|
//#endregion
|
|
21292
|
-
//#region src/primitives/select.tsx
|
|
21293
|
-
/**
|
|
21294
|
-
* A native `<select>` styled to match the rest of the field set.
|
|
21295
|
-
*
|
|
21296
|
-
* `appearance-none` plus an explicit `bg-surface` / `text-foreground` is what
|
|
21297
|
-
* makes it match — and it is also what USED to make a disabled one a lie. A
|
|
21298
|
-
* browser greys a disabled control by rendering its own background and text
|
|
21299
|
-
* colour; all three of those are overridden here, so a `disabled` dropdown
|
|
21300
|
-
* looked exactly like a live one and simply ignored the click. That is the
|
|
21301
|
-
* whole of the "the language selector does not open" report of 2026-08-15:
|
|
21302
|
-
* the AI section's switch was off, every control under it was correctly inert,
|
|
21303
|
-
* and nothing on screen said so.
|
|
21304
|
-
*
|
|
21305
|
-
* `disabled:` classes belong HERE and not on each call site: there are dozens
|
|
21306
|
-
* of `<Select disabled>` in this app and the one that gets forgotten is the one
|
|
21307
|
-
* that generates the ticket. Guarded by `disabled-affordance.test.tsx`.
|
|
21308
|
-
*/
|
|
21309
|
-
var Select = forwardRef(({ className, options, disabled, ...props }, ref) => {
|
|
21310
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
21311
|
-
className: "relative w-full",
|
|
21312
|
-
children: [/* @__PURE__ */ jsx("select", {
|
|
21313
|
-
ref,
|
|
21314
|
-
disabled,
|
|
21315
|
-
className: cn("h-7 w-full px-2.5 text-xs bg-surface border border-border rounded-md text-foreground appearance-none pr-7 focus:outline-none focus:ring-1 focus:ring-primary", "disabled:opacity-50 disabled:cursor-not-allowed", className),
|
|
21316
|
-
...props,
|
|
21317
|
-
children: options.map((option) => /* @__PURE__ */ jsx("option", {
|
|
21318
|
-
value: option.value,
|
|
21319
|
-
children: option.label
|
|
21320
|
-
}, option.value))
|
|
21321
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
21322
|
-
className: cn("absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none text-foreground-muted", disabled === true ? "opacity-50" : ""),
|
|
21323
|
-
children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" })
|
|
21324
|
-
})]
|
|
21325
|
-
});
|
|
21326
|
-
});
|
|
21327
|
-
Select.displayName = "Select";
|
|
21328
|
-
//#endregion
|
|
21329
21329
|
//#region src/primitives/skeleton.tsx
|
|
21330
21330
|
var Skeleton = forwardRef(({ className, ...props }, ref) => {
|
|
21331
21331
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -28298,7 +28298,7 @@ function TimezoneSelector({ value, onChange, disabled }) {
|
|
|
28298
28298
|
var INPUT_CLASS = "w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-xs text-foreground focus:border-primary focus:ring-1 focus:ring-primary/30 outline-none disabled:opacity-50 disabled:cursor-not-allowed";
|
|
28299
28299
|
var LABEL_CLASS$1 = "block text-[11px] font-medium text-foreground mb-1 break-words";
|
|
28300
28300
|
var DESC_CLASS = "text-[10px] text-foreground-subtle mt-0.5 break-words";
|
|
28301
|
-
function FieldWrapper({ label, description, required, span, children, translationFn }) {
|
|
28301
|
+
function FieldWrapper({ label, description, required, span, htmlFor, children, translationFn }) {
|
|
28302
28302
|
const colSpanClass = span === 2 ? "col-span-1 @[480px]:col-span-2" : span === 3 ? "col-span-1 @[480px]:col-span-2 @[880px]:col-span-3" : span === 4 ? "col-span-1 @[480px]:col-span-2 @[1140px]:col-span-4" : "col-span-1";
|
|
28303
28303
|
const resolvedLabel = resolveLabel(label, translationFn);
|
|
28304
28304
|
const resolvedDescription = resolveLabel(description, translationFn);
|
|
@@ -28308,6 +28308,7 @@ function FieldWrapper({ label, description, required, span, children, translatio
|
|
|
28308
28308
|
children: [
|
|
28309
28309
|
hasLabel && /* @__PURE__ */ jsxs("label", {
|
|
28310
28310
|
className: LABEL_CLASS$1,
|
|
28311
|
+
...htmlFor !== void 0 ? { htmlFor } : {},
|
|
28311
28312
|
children: [resolvedLabel, required && /* @__PURE__ */ jsx("span", {
|
|
28312
28313
|
className: "text-danger ml-0.5",
|
|
28313
28314
|
children: "*"
|
|
@@ -28476,24 +28477,27 @@ function BooleanField({ field, value, onChange, disabled, translationFn }) {
|
|
|
28476
28477
|
}
|
|
28477
28478
|
function SelectField({ field, value, onChange, disabled, translationFn }) {
|
|
28478
28479
|
const hasEmptyOption = field.options.some((o) => o.value === "");
|
|
28480
|
+
const options = [...!field.required && !hasEmptyOption ? [{
|
|
28481
|
+
value: "",
|
|
28482
|
+
label: "— Select —"
|
|
28483
|
+
}] : [], ...field.options.map((opt) => ({
|
|
28484
|
+
value: opt.value,
|
|
28485
|
+
label: opt.label
|
|
28486
|
+
}))];
|
|
28487
|
+
const controlId = `config-select-${field.key}`;
|
|
28479
28488
|
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
28480
28489
|
label: field.label,
|
|
28481
28490
|
description: field.description,
|
|
28482
28491
|
required: field.required,
|
|
28483
28492
|
span: field.span,
|
|
28493
|
+
htmlFor: controlId,
|
|
28484
28494
|
translationFn,
|
|
28485
|
-
children: /* @__PURE__ */
|
|
28486
|
-
|
|
28495
|
+
children: /* @__PURE__ */ jsx(Select, {
|
|
28496
|
+
id: controlId,
|
|
28497
|
+
options,
|
|
28487
28498
|
value: value === void 0 || value === null ? "" : String(value),
|
|
28488
28499
|
disabled: disabled || field.disabled,
|
|
28489
|
-
onChange: (e) => onChange(e.target.value)
|
|
28490
|
-
children: [!field.required && !hasEmptyOption && /* @__PURE__ */ jsx("option", {
|
|
28491
|
-
value: "",
|
|
28492
|
-
children: "— Select —"
|
|
28493
|
-
}), field.options.map((opt) => /* @__PURE__ */ jsx("option", {
|
|
28494
|
-
value: opt.value,
|
|
28495
|
-
children: opt.label
|
|
28496
|
-
}, opt.value))]
|
|
28500
|
+
onChange: (e) => onChange(e.target.value)
|
|
28497
28501
|
})
|
|
28498
28502
|
});
|
|
28499
28503
|
}
|
|
@@ -30101,11 +30105,27 @@ function resolveLabel(value, t) {
|
|
|
30101
30105
|
if (t && isI18nKey(value)) return t(value);
|
|
30102
30106
|
return value;
|
|
30103
30107
|
}
|
|
30108
|
+
/**
|
|
30109
|
+
* Container-query driven responsive grid. Each section is its own
|
|
30110
|
+
* `@container` so the column count adapts to the SECTION's width
|
|
30111
|
+
* (not the viewport) — important inside narrow hosts like the
|
|
30112
|
+
* device-detail right column / ItemFormDialog. Behavior:
|
|
30113
|
+
* columns=1 → always 1 col
|
|
30114
|
+
* columns=2 (default) → 1 col below 480px container, 2 cols above
|
|
30115
|
+
* columns=3 → 1 → 2 → 3 col at 480px / 880px
|
|
30116
|
+
* columns=4 → 1 → 2 → 4 col at 480px / 1140px
|
|
30117
|
+
*/
|
|
30118
|
+
function sectionGridClass(columns) {
|
|
30119
|
+
const cols = columns ?? 2;
|
|
30120
|
+
if (cols === 1) return "grid-cols-1";
|
|
30121
|
+
if (cols === 3) return "grid-cols-1 @[480px]:grid-cols-2 @[880px]:grid-cols-3";
|
|
30122
|
+
if (cols === 4) return "grid-cols-1 @[480px]:grid-cols-2 @[1140px]:grid-cols-4";
|
|
30123
|
+
return "grid-cols-1 @[480px]:grid-cols-2";
|
|
30124
|
+
}
|
|
30104
30125
|
function SectionCard$1({ section, values, onChange, disabled, translationFn, onTestField, probeResults }) {
|
|
30105
30126
|
const [collapsed, setCollapsed] = useState(section.defaultCollapsed ?? false);
|
|
30106
30127
|
const isAccordion = section.style === "accordion";
|
|
30107
|
-
const
|
|
30108
|
-
const gridClass = columns === 1 ? "grid-cols-1" : columns === 3 ? "grid-cols-1 @[480px]:grid-cols-2 @[880px]:grid-cols-3" : columns === 4 ? "grid-cols-1 @[480px]:grid-cols-2 @[1140px]:grid-cols-4" : "grid-cols-1 @[480px]:grid-cols-2";
|
|
30128
|
+
const gridClass = sectionGridClass(section.columns);
|
|
30109
30129
|
return /* @__PURE__ */ jsxs("div", {
|
|
30110
30130
|
className: "@container rounded-lg border border-border bg-surface p-3",
|
|
30111
30131
|
style: { containerType: "inline-size" },
|
|
@@ -30145,16 +30165,23 @@ function ConfigFormBuilder({ schema, values, onChange, disabled, translationFn,
|
|
|
30145
30165
|
onClearProbe?.(key);
|
|
30146
30166
|
};
|
|
30147
30167
|
if (bare) return /* @__PURE__ */ jsx("div", {
|
|
30148
|
-
className: "
|
|
30149
|
-
children: schema.sections.
|
|
30150
|
-
|
|
30151
|
-
|
|
30152
|
-
|
|
30153
|
-
|
|
30154
|
-
|
|
30155
|
-
|
|
30156
|
-
|
|
30157
|
-
|
|
30168
|
+
className: "space-y-4",
|
|
30169
|
+
children: schema.sections.map((section) => /* @__PURE__ */ jsx("div", {
|
|
30170
|
+
className: "@container",
|
|
30171
|
+
style: { containerType: "inline-size" },
|
|
30172
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
30173
|
+
className: `grid ${sectionGridClass(section.columns)} gap-x-3 gap-y-2.5`,
|
|
30174
|
+
children: section.fields.map((field) => /* @__PURE__ */ jsx(FormField, {
|
|
30175
|
+
field,
|
|
30176
|
+
values,
|
|
30177
|
+
onChange: handleFieldChange,
|
|
30178
|
+
disabled,
|
|
30179
|
+
translationFn,
|
|
30180
|
+
onTestField,
|
|
30181
|
+
externalProbe: probeResults?.[field.key]
|
|
30182
|
+
}, field.key))
|
|
30183
|
+
})
|
|
30184
|
+
}, section.id))
|
|
30158
30185
|
});
|
|
30159
30186
|
return /* @__PURE__ */ jsx("div", {
|
|
30160
30187
|
className: "space-y-4",
|