@flanksource/clicky-ui 0.2.11 → 0.2.13

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.
Files changed (188) hide show
  1. package/dist/components/Combobox.cjs +47 -33
  2. package/dist/components/Combobox.cjs.map +1 -1
  3. package/dist/components/Combobox.d.ts +12 -0
  4. package/dist/components/Combobox.d.ts.map +1 -1
  5. package/dist/components/Combobox.js +48 -34
  6. package/dist/components/Combobox.js.map +1 -1
  7. package/dist/components/IconButton.cjs +26 -0
  8. package/dist/components/IconButton.cjs.map +1 -0
  9. package/dist/components/IconButton.d.ts +25 -0
  10. package/dist/components/IconButton.d.ts.map +1 -0
  11. package/dist/components/IconButton.js +26 -0
  12. package/dist/components/IconButton.js.map +1 -0
  13. package/dist/components/JsonSchemaForm.cjs +1 -1
  14. package/dist/components/JsonSchemaForm.cjs.map +1 -1
  15. package/dist/components/JsonSchemaForm.js +1 -1
  16. package/dist/components/JsonSchemaForm.js.map +1 -1
  17. package/dist/components/SecretKeySelector.cjs +151 -0
  18. package/dist/components/SecretKeySelector.cjs.map +1 -0
  19. package/dist/components/SecretKeySelector.d.ts +47 -0
  20. package/dist/components/SecretKeySelector.d.ts.map +1 -0
  21. package/dist/components/SecretKeySelector.js +151 -0
  22. package/dist/components/SecretKeySelector.js.map +1 -0
  23. package/dist/components/WorkloadPicker.cjs +144 -0
  24. package/dist/components/WorkloadPicker.cjs.map +1 -0
  25. package/dist/components/WorkloadPicker.d.ts +52 -0
  26. package/dist/components/WorkloadPicker.d.ts.map +1 -0
  27. package/dist/components/WorkloadPicker.js +144 -0
  28. package/dist/components/WorkloadPicker.js.map +1 -0
  29. package/dist/components/icon-menu-picker.cjs +83 -112
  30. package/dist/components/icon-menu-picker.cjs.map +1 -1
  31. package/dist/components/icon-menu-picker.d.ts.map +1 -1
  32. package/dist/components/icon-menu-picker.js +85 -114
  33. package/dist/components/icon-menu-picker.js.map +1 -1
  34. package/dist/components/json-schema-form-fields.cjs +9 -7
  35. package/dist/components/json-schema-form-fields.cjs.map +1 -1
  36. package/dist/components/json-schema-form-fields.d.ts.map +1 -1
  37. package/dist/components/json-schema-form-fields.js +9 -7
  38. package/dist/components/json-schema-form-fields.js.map +1 -1
  39. package/dist/components/json-schema-form-types.d.ts +8 -4
  40. package/dist/components/json-schema-form-types.d.ts.map +1 -1
  41. package/dist/components.cjs +10 -0
  42. package/dist/components.cjs.map +1 -1
  43. package/dist/components.d.ts +3 -0
  44. package/dist/components.d.ts.map +1 -1
  45. package/dist/components.js +11 -1
  46. package/dist/components.js.map +1 -1
  47. package/dist/data/Clicky.cjs +1 -1
  48. package/dist/data/Clicky.cjs.map +1 -1
  49. package/dist/data/Clicky.js +1 -1
  50. package/dist/data/Clicky.js.map +1 -1
  51. package/dist/data/LogDetails.cjs +4 -2
  52. package/dist/data/LogDetails.cjs.map +1 -1
  53. package/dist/data/LogDetails.js +4 -2
  54. package/dist/data/LogDetails.js.map +1 -1
  55. package/dist/data/Version.cjs +42 -0
  56. package/dist/data/Version.cjs.map +1 -0
  57. package/dist/data/Version.d.ts +36 -0
  58. package/dist/data/Version.d.ts.map +1 -0
  59. package/dist/data/Version.js +42 -0
  60. package/dist/data/Version.js.map +1 -0
  61. package/dist/data/test-runner/TestDetailPanel.cjs +165 -0
  62. package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -0
  63. package/dist/data/test-runner/TestDetailPanel.d.ts +7 -0
  64. package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -0
  65. package/dist/data/test-runner/TestDetailPanel.js +165 -0
  66. package/dist/data/test-runner/TestDetailPanel.js.map +1 -0
  67. package/dist/data/test-runner/TestFailureDetail.cjs +78 -0
  68. package/dist/data/test-runner/TestFailureDetail.cjs.map +1 -0
  69. package/dist/data/test-runner/TestFailureDetail.d.ts +14 -0
  70. package/dist/data/test-runner/TestFailureDetail.d.ts.map +1 -0
  71. package/dist/data/test-runner/TestFailureDetail.js +78 -0
  72. package/dist/data/test-runner/TestFailureDetail.js.map +1 -0
  73. package/dist/data/test-runner/TestFilterBar.cjs +108 -0
  74. package/dist/data/test-runner/TestFilterBar.cjs.map +1 -0
  75. package/dist/data/test-runner/TestFilterBar.d.ts +15 -0
  76. package/dist/data/test-runner/TestFilterBar.d.ts.map +1 -0
  77. package/dist/data/test-runner/TestFilterBar.js +108 -0
  78. package/dist/data/test-runner/TestFilterBar.js.map +1 -0
  79. package/dist/data/test-runner/TestRunSummary.cjs +81 -0
  80. package/dist/data/test-runner/TestRunSummary.cjs.map +1 -0
  81. package/dist/data/test-runner/TestRunSummary.d.ts +19 -0
  82. package/dist/data/test-runner/TestRunSummary.d.ts.map +1 -0
  83. package/dist/data/test-runner/TestRunSummary.js +81 -0
  84. package/dist/data/test-runner/TestRunSummary.js.map +1 -0
  85. package/dist/data/test-runner/TestRunner.cjs +143 -0
  86. package/dist/data/test-runner/TestRunner.cjs.map +1 -0
  87. package/dist/data/test-runner/TestRunner.d.ts +42 -0
  88. package/dist/data/test-runner/TestRunner.d.ts.map +1 -0
  89. package/dist/data/test-runner/TestRunner.fixtures.d.ts +26 -0
  90. package/dist/data/test-runner/TestRunner.fixtures.d.ts.map +1 -0
  91. package/dist/data/test-runner/TestRunner.js +143 -0
  92. package/dist/data/test-runner/TestRunner.js.map +1 -0
  93. package/dist/data/test-runner/TestTree.cjs +42 -0
  94. package/dist/data/test-runner/TestTree.cjs.map +1 -0
  95. package/dist/data/test-runner/TestTree.d.ts +9 -0
  96. package/dist/data/test-runner/TestTree.d.ts.map +1 -0
  97. package/dist/data/test-runner/TestTree.js +42 -0
  98. package/dist/data/test-runner/TestTree.js.map +1 -0
  99. package/dist/data/test-runner/TestTreeNode.cjs +99 -0
  100. package/dist/data/test-runner/TestTreeNode.cjs.map +1 -0
  101. package/dist/data/test-runner/TestTreeNode.d.ts +12 -0
  102. package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -0
  103. package/dist/data/test-runner/TestTreeNode.js +99 -0
  104. package/dist/data/test-runner/TestTreeNode.js.map +1 -0
  105. package/dist/data/test-runner/adapter.cjs +57 -0
  106. package/dist/data/test-runner/adapter.cjs.map +1 -0
  107. package/dist/data/test-runner/adapter.d.ts +60 -0
  108. package/dist/data/test-runner/adapter.d.ts.map +1 -0
  109. package/dist/data/test-runner/adapter.js +57 -0
  110. package/dist/data/test-runner/adapter.js.map +1 -0
  111. package/dist/data/test-runner/context.cjs +15 -0
  112. package/dist/data/test-runner/context.cjs.map +1 -0
  113. package/dist/data/test-runner/context.d.ts +40 -0
  114. package/dist/data/test-runner/context.d.ts.map +1 -0
  115. package/dist/data/test-runner/context.js +15 -0
  116. package/dist/data/test-runner/context.js.map +1 -0
  117. package/dist/data/test-runner/filterState.cjs +47 -0
  118. package/dist/data/test-runner/filterState.cjs.map +1 -0
  119. package/dist/data/test-runner/filterState.d.ts +14 -0
  120. package/dist/data/test-runner/filterState.d.ts.map +1 -0
  121. package/dist/data/test-runner/filterState.js +47 -0
  122. package/dist/data/test-runner/filterState.js.map +1 -0
  123. package/dist/data/test-runner/frameworkIcon.cjs +27 -0
  124. package/dist/data/test-runner/frameworkIcon.cjs.map +1 -0
  125. package/dist/data/test-runner/frameworkIcon.d.ts +3 -0
  126. package/dist/data/test-runner/frameworkIcon.d.ts.map +1 -0
  127. package/dist/data/test-runner/frameworkIcon.js +27 -0
  128. package/dist/data/test-runner/frameworkIcon.js.map +1 -0
  129. package/dist/data/test-runner/index.d.ts +14 -0
  130. package/dist/data/test-runner/index.d.ts.map +1 -0
  131. package/dist/data/test-runner/status.cjs +234 -0
  132. package/dist/data/test-runner/status.cjs.map +1 -0
  133. package/dist/data/test-runner/status.d.ts +52 -0
  134. package/dist/data/test-runner/status.d.ts.map +1 -0
  135. package/dist/data/test-runner/status.js +234 -0
  136. package/dist/data/test-runner/status.js.map +1 -0
  137. package/dist/data/test-runner/types.d.ts +148 -0
  138. package/dist/data/test-runner/types.d.ts.map +1 -0
  139. package/dist/data/version-info.cjs +26 -0
  140. package/dist/data/version-info.cjs.map +1 -0
  141. package/dist/data/version-info.d.ts +11 -0
  142. package/dist/data/version-info.d.ts.map +1 -0
  143. package/dist/data/version-info.js +26 -0
  144. package/dist/data/version-info.js.map +1 -0
  145. package/dist/data.cjs +49 -0
  146. package/dist/data.cjs.map +1 -1
  147. package/dist/data.d.ts +3 -0
  148. package/dist/data.d.ts.map +1 -1
  149. package/dist/data.js +50 -1
  150. package/dist/data.js.map +1 -1
  151. package/dist/index.cjs +59 -0
  152. package/dist/index.cjs.map +1 -1
  153. package/dist/index.js +60 -1
  154. package/dist/index.js.map +1 -1
  155. package/dist/layout/SplitPane.cjs +34 -27
  156. package/dist/layout/SplitPane.cjs.map +1 -1
  157. package/dist/layout/SplitPane.d.ts.map +1 -1
  158. package/dist/layout/SplitPane.js +34 -27
  159. package/dist/layout/SplitPane.js.map +1 -1
  160. package/dist/overlay/DropdownMenu.cjs +57 -30
  161. package/dist/overlay/DropdownMenu.cjs.map +1 -1
  162. package/dist/overlay/DropdownMenu.d.ts.map +1 -1
  163. package/dist/overlay/DropdownMenu.js +57 -30
  164. package/dist/overlay/DropdownMenu.js.map +1 -1
  165. package/dist/overlay/Modal.cjs +62 -6
  166. package/dist/overlay/Modal.cjs.map +1 -1
  167. package/dist/overlay/Modal.d.ts +19 -1
  168. package/dist/overlay/Modal.d.ts.map +1 -1
  169. package/dist/overlay/Modal.js +62 -6
  170. package/dist/overlay/Modal.js.map +1 -1
  171. package/dist/rpc/CommandForm.cjs +2 -2
  172. package/dist/rpc/CommandForm.cjs.map +1 -1
  173. package/dist/rpc/CommandForm.d.ts.map +1 -1
  174. package/dist/rpc/CommandForm.js +3 -3
  175. package/dist/rpc/CommandForm.js.map +1 -1
  176. package/dist/rpc/formMetadata.cjs +5 -4
  177. package/dist/rpc/formMetadata.cjs.map +1 -1
  178. package/dist/rpc/formMetadata.d.ts.map +1 -1
  179. package/dist/rpc/formMetadata.js +6 -5
  180. package/dist/rpc/formMetadata.js.map +1 -1
  181. package/dist/rpc/types.cjs +4 -0
  182. package/dist/rpc/types.cjs.map +1 -1
  183. package/dist/rpc/types.d.ts +10 -0
  184. package/dist/rpc/types.d.ts.map +1 -1
  185. package/dist/rpc/types.js +5 -1
  186. package/dist/rpc/types.js.map +1 -1
  187. package/dist/styles.css +1 -1
  188. package/package.json +3 -1
@@ -0,0 +1,26 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../lib/utils.js";
4
+ import { Icon } from "../data/Icon.js";
5
+ const IconButton = forwardRef(
6
+ ({ icon, label, iconClassName, className, type = "button", ...props }, ref) => /* @__PURE__ */ jsx(
7
+ "button",
8
+ {
9
+ ref,
10
+ type,
11
+ "aria-label": label,
12
+ title: label,
13
+ className: cn(
14
+ "inline-flex shrink-0 items-center justify-center rounded-sm border-0 bg-transparent p-0 text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
15
+ className
16
+ ),
17
+ ...props,
18
+ children: /* @__PURE__ */ jsx(Icon, { icon, className: cn("text-sm", iconClassName) })
19
+ }
20
+ )
21
+ );
22
+ IconButton.displayName = "IconButton";
23
+ export {
24
+ IconButton
25
+ };
26
+ //# sourceMappingURL=IconButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IconButton.js","sources":["../../src/components/IconButton.tsx"],"sourcesContent":["import { forwardRef, type ButtonHTMLAttributes } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\n\nexport type IconButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {\n /** Imported icon component to render. */\n icon: StaticIconComponent;\n /** Accessible name; drives both aria-label and the tooltip title. */\n label: string;\n /** Extra classes for the glyph (e.g. size). */\n iconClassName?: string;\n};\n\n/**\n * A borderless, background-free icon button: the hover effect is on the glyph\n * itself (color), not a surrounding chip. Distinct from `Button` (which is a\n * box-shaped control). Icons render with `currentColor`, so `hover:text-*`\n * recolors the glyph.\n */\nexport const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(\n ({ icon, label, iconClassName, className, type = \"button\", ...props }, ref) => (\n <button\n ref={ref}\n type={type}\n aria-label={label}\n title={label}\n className={cn(\n \"inline-flex shrink-0 items-center justify-center rounded-sm border-0 bg-transparent p-0 text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\",\n className,\n )}\n {...props}\n >\n <Icon icon={icon} className={cn(\"text-sm\", iconClassName)} />\n </button>\n ),\n);\nIconButton.displayName = \"IconButton\";\n"],"names":[],"mappings":";;;;AAmBO,MAAM,aAAa;AAAA,EACxB,CAAC,EAAE,MAAM,OAAO,eAAe,WAAW,OAAO,UAAU,GAAG,SAAS,QACrE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA,cAAY;AAAA,MACZ,OAAO;AAAA,MACP,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEJ,8BAAC,MAAA,EAAK,MAAY,WAAW,GAAG,WAAW,aAAa,EAAA,CAAG;AAAA,IAAA;AAAA,EAAA;AAGjE;AACA,WAAW,cAAc;"}
@@ -5,7 +5,7 @@ const utils = require("../lib/utils.cjs");
5
5
  const jsonSchemaFormRender = require("./json-schema-form-render.cjs");
6
6
  const jsonSchemaFormSize = require("./json-schema-form-size.cjs");
7
7
  const DEFAULT_LABEL_MAX_WIDTH = "40ch";
8
- const DEFAULT_VALUE_MAX_WIDTH = "400px";
8
+ const DEFAULT_VALUE_MAX_WIDTH = "600px";
9
9
  function resolveFormLayout(layout, inline) {
10
10
  const base = layout ?? { mode: inline ? "inline" : "stacked" };
11
11
  if (base.mode !== "inline") return { mode: "stacked" };
@@ -1 +1 @@
1
- {"version":3,"file":"JsonSchemaForm.cjs","sources":["../../src/components/JsonSchemaForm.tsx"],"sourcesContent":["import { cn } from \"../lib/utils\";\nimport { renderObjectFields, type RenderContext } from \"./json-schema-form-render\";\nimport {\n DEFAULT_FORM_SIZE,\n fieldInnerGapClass,\n inlineRowGapClass,\n labelSizeClass,\n stackedRowGapClass,\n} from \"./json-schema-form-size\";\nimport type { FormLayout, JsonSchemaFormProps } from \"./json-schema-form-types\";\n\nconst DEFAULT_LABEL_MAX_WIDTH = \"40ch\";\nconst DEFAULT_VALUE_MAX_WIDTH = \"400px\";\n\n// resolveFormLayout maps the `layout`/`inline` props to a single resolved\n// FormLayout. An explicit `layout` wins; otherwise `inline` selects the inline\n// preset. Width caps are only filled for inline mode.\nfunction resolveFormLayout(layout: FormLayout | undefined, inline: boolean): FormLayout {\n const base = layout ?? { mode: inline ? \"inline\" : \"stacked\" };\n if (base.mode !== \"inline\") return { mode: \"stacked\" };\n return {\n mode: \"inline\",\n labelMaxWidth: base.labelMaxWidth ?? DEFAULT_LABEL_MAX_WIDTH,\n valueMaxWidth: base.valueMaxWidth ?? DEFAULT_VALUE_MAX_WIDTH,\n };\n}\n\n// JsonSchemaForm renders an object subschema as a form: one control per\n// (effective) property. It resolves if/then conditionals internally and recurses\n// through array items and object/map values; every other behaviour is layered on\n// by the pre/post extension functions the consumer supplies. The component holds\n// no domain knowledge.\nexport function JsonSchemaForm({\n schema,\n value,\n onChange,\n readOnly = false,\n inline = false,\n layout,\n size = DEFAULT_FORM_SIZE,\n idPrefix,\n hideReadOnlyFields = false,\n hiddenKeys,\n requiredFirst = false,\n title,\n pre,\n post,\n}: JsonSchemaFormProps) {\n const resolvedLayout = resolveFormLayout(layout, inline);\n const ctx: RenderContext = {\n readOnly,\n hideReadOnlyFields,\n layout: resolvedLayout,\n size,\n requiredFirst,\n pre: pre ?? [],\n post: post ?? [],\n depth: 0,\n ...(idPrefix ? { idPrefix } : {}),\n };\n const rows = renderObjectFields(schema, value, onChange, ctx, hiddenKeys ? { hiddenKeys } : undefined);\n\n const rowGap = resolvedLayout.mode === \"inline\" ? inlineRowGapClass[size] : stackedRowGapClass[size];\n return (\n <div className={cn(\"flex flex-col\", fieldInnerGapClass[size])}>\n {title && <h3 className={cn(\"font-semibold\", labelSizeClass[size])}>{title}</h3>}\n <div className={cn(\"grid\", rowGap)}>{rows}</div>\n </div>\n );\n}\n"],"names":["DEFAULT_FORM_SIZE","renderObjectFields","inlineRowGapClass","stackedRowGapClass","jsxs","cn","fieldInnerGapClass","jsx","labelSizeClass"],"mappings":";;;;;;AAWA,MAAM,0BAA0B;AAChC,MAAM,0BAA0B;AAKhC,SAAS,kBAAkB,QAAgC,QAA6B;AACtF,QAAM,OAAO,UAAU,EAAE,MAAM,SAAS,WAAW,UAAA;AACnD,MAAI,KAAK,SAAS,SAAU,QAAO,EAAE,MAAM,UAAA;AAC3C,SAAO;AAAA,IACL,MAAM;AAAA,IACN,eAAe,KAAK,iBAAiB;AAAA,IACrC,eAAe,KAAK,iBAAiB;AAAA,EAAA;AAEzC;AAOO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,SAAS;AAAA,EACT;AAAA,EACA,OAAOA,mBAAAA;AAAAA,EACP;AAAA,EACA,qBAAqB;AAAA,EACrB;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,QAAM,iBAAiB,kBAAkB,QAAQ,MAAM;AACvD,QAAM,MAAqB;AAAA,IACzB;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,KAAK,OAAO,CAAA;AAAA,IACZ,MAAM,QAAQ,CAAA;AAAA,IACd,OAAO;AAAA,IACP,GAAI,WAAW,EAAE,aAAa,CAAA;AAAA,EAAC;AAEjC,QAAM,OAAOC,qBAAAA,mBAAmB,QAAQ,OAAO,UAAU,KAAK,aAAa,EAAE,WAAA,IAAe,MAAS;AAErG,QAAM,SAAS,eAAe,SAAS,WAAWC,mBAAAA,kBAAkB,IAAI,IAAIC,mBAAAA,mBAAmB,IAAI;AACnG,SACEC,2BAAAA,KAAC,SAAI,WAAWC,MAAAA,GAAG,iBAAiBC,sCAAmB,IAAI,CAAC,GACzD,UAAA;AAAA,IAAA,SAASC,2BAAAA,IAAC,QAAG,WAAWF,MAAAA,GAAG,iBAAiBG,mBAAAA,eAAe,IAAI,CAAC,GAAI,UAAA,MAAA,CAAM;AAAA,mCAC1E,OAAA,EAAI,WAAWH,MAAAA,GAAG,QAAQ,MAAM,GAAI,UAAA,KAAA,CAAK;AAAA,EAAA,GAC5C;AAEJ;;"}
1
+ {"version":3,"file":"JsonSchemaForm.cjs","sources":["../../src/components/JsonSchemaForm.tsx"],"sourcesContent":["import { cn } from \"../lib/utils\";\nimport { renderObjectFields, type RenderContext } from \"./json-schema-form-render\";\nimport {\n DEFAULT_FORM_SIZE,\n fieldInnerGapClass,\n inlineRowGapClass,\n labelSizeClass,\n stackedRowGapClass,\n} from \"./json-schema-form-size\";\nimport type { FormLayout, JsonSchemaFormProps } from \"./json-schema-form-types\";\n\nconst DEFAULT_LABEL_MAX_WIDTH = \"40ch\";\nconst DEFAULT_VALUE_MAX_WIDTH = \"600px\";\n\n// resolveFormLayout maps the `layout`/`inline` props to a single resolved\n// FormLayout. An explicit `layout` wins; otherwise `inline` selects the inline\n// preset. Width caps are only filled for inline mode.\nfunction resolveFormLayout(layout: FormLayout | undefined, inline: boolean): FormLayout {\n const base = layout ?? { mode: inline ? \"inline\" : \"stacked\" };\n if (base.mode !== \"inline\") return { mode: \"stacked\" };\n return {\n mode: \"inline\",\n labelMaxWidth: base.labelMaxWidth ?? DEFAULT_LABEL_MAX_WIDTH,\n valueMaxWidth: base.valueMaxWidth ?? DEFAULT_VALUE_MAX_WIDTH,\n };\n}\n\n// JsonSchemaForm renders an object subschema as a form: one control per\n// (effective) property. It resolves if/then conditionals internally and recurses\n// through array items and object/map values; every other behaviour is layered on\n// by the pre/post extension functions the consumer supplies. The component holds\n// no domain knowledge.\nexport function JsonSchemaForm({\n schema,\n value,\n onChange,\n readOnly = false,\n inline = false,\n layout,\n size = DEFAULT_FORM_SIZE,\n idPrefix,\n hideReadOnlyFields = false,\n hiddenKeys,\n requiredFirst = false,\n title,\n pre,\n post,\n}: JsonSchemaFormProps) {\n const resolvedLayout = resolveFormLayout(layout, inline);\n const ctx: RenderContext = {\n readOnly,\n hideReadOnlyFields,\n layout: resolvedLayout,\n size,\n requiredFirst,\n pre: pre ?? [],\n post: post ?? [],\n depth: 0,\n ...(idPrefix ? { idPrefix } : {}),\n };\n const rows = renderObjectFields(schema, value, onChange, ctx, hiddenKeys ? { hiddenKeys } : undefined);\n\n const rowGap = resolvedLayout.mode === \"inline\" ? inlineRowGapClass[size] : stackedRowGapClass[size];\n return (\n <div className={cn(\"flex flex-col\", fieldInnerGapClass[size])}>\n {title && <h3 className={cn(\"font-semibold\", labelSizeClass[size])}>{title}</h3>}\n <div className={cn(\"grid\", rowGap)}>{rows}</div>\n </div>\n );\n}\n"],"names":["DEFAULT_FORM_SIZE","renderObjectFields","inlineRowGapClass","stackedRowGapClass","jsxs","cn","fieldInnerGapClass","jsx","labelSizeClass"],"mappings":";;;;;;AAWA,MAAM,0BAA0B;AAChC,MAAM,0BAA0B;AAKhC,SAAS,kBAAkB,QAAgC,QAA6B;AACtF,QAAM,OAAO,UAAU,EAAE,MAAM,SAAS,WAAW,UAAA;AACnD,MAAI,KAAK,SAAS,SAAU,QAAO,EAAE,MAAM,UAAA;AAC3C,SAAO;AAAA,IACL,MAAM;AAAA,IACN,eAAe,KAAK,iBAAiB;AAAA,IACrC,eAAe,KAAK,iBAAiB;AAAA,EAAA;AAEzC;AAOO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,SAAS;AAAA,EACT;AAAA,EACA,OAAOA,mBAAAA;AAAAA,EACP;AAAA,EACA,qBAAqB;AAAA,EACrB;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,QAAM,iBAAiB,kBAAkB,QAAQ,MAAM;AACvD,QAAM,MAAqB;AAAA,IACzB;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,KAAK,OAAO,CAAA;AAAA,IACZ,MAAM,QAAQ,CAAA;AAAA,IACd,OAAO;AAAA,IACP,GAAI,WAAW,EAAE,aAAa,CAAA;AAAA,EAAC;AAEjC,QAAM,OAAOC,qBAAAA,mBAAmB,QAAQ,OAAO,UAAU,KAAK,aAAa,EAAE,WAAA,IAAe,MAAS;AAErG,QAAM,SAAS,eAAe,SAAS,WAAWC,mBAAAA,kBAAkB,IAAI,IAAIC,mBAAAA,mBAAmB,IAAI;AACnG,SACEC,2BAAAA,KAAC,SAAI,WAAWC,MAAAA,GAAG,iBAAiBC,sCAAmB,IAAI,CAAC,GACzD,UAAA;AAAA,IAAA,SAASC,2BAAAA,IAAC,QAAG,WAAWF,MAAAA,GAAG,iBAAiBG,mBAAAA,eAAe,IAAI,CAAC,GAAI,UAAA,MAAA,CAAM;AAAA,mCAC1E,OAAA,EAAI,WAAWH,MAAAA,GAAG,QAAQ,MAAM,GAAI,UAAA,KAAA,CAAK;AAAA,EAAA,GAC5C;AAEJ;;"}
@@ -3,7 +3,7 @@ import { cn } from "../lib/utils.js";
3
3
  import { renderObjectFields } from "./json-schema-form-render.js";
4
4
  import { inlineRowGapClass, DEFAULT_FORM_SIZE, stackedRowGapClass, labelSizeClass, fieldInnerGapClass } from "./json-schema-form-size.js";
5
5
  const DEFAULT_LABEL_MAX_WIDTH = "40ch";
6
- const DEFAULT_VALUE_MAX_WIDTH = "400px";
6
+ const DEFAULT_VALUE_MAX_WIDTH = "600px";
7
7
  function resolveFormLayout(layout, inline) {
8
8
  const base = layout ?? { mode: inline ? "inline" : "stacked" };
9
9
  if (base.mode !== "inline") return { mode: "stacked" };
@@ -1 +1 @@
1
- {"version":3,"file":"JsonSchemaForm.js","sources":["../../src/components/JsonSchemaForm.tsx"],"sourcesContent":["import { cn } from \"../lib/utils\";\nimport { renderObjectFields, type RenderContext } from \"./json-schema-form-render\";\nimport {\n DEFAULT_FORM_SIZE,\n fieldInnerGapClass,\n inlineRowGapClass,\n labelSizeClass,\n stackedRowGapClass,\n} from \"./json-schema-form-size\";\nimport type { FormLayout, JsonSchemaFormProps } from \"./json-schema-form-types\";\n\nconst DEFAULT_LABEL_MAX_WIDTH = \"40ch\";\nconst DEFAULT_VALUE_MAX_WIDTH = \"400px\";\n\n// resolveFormLayout maps the `layout`/`inline` props to a single resolved\n// FormLayout. An explicit `layout` wins; otherwise `inline` selects the inline\n// preset. Width caps are only filled for inline mode.\nfunction resolveFormLayout(layout: FormLayout | undefined, inline: boolean): FormLayout {\n const base = layout ?? { mode: inline ? \"inline\" : \"stacked\" };\n if (base.mode !== \"inline\") return { mode: \"stacked\" };\n return {\n mode: \"inline\",\n labelMaxWidth: base.labelMaxWidth ?? DEFAULT_LABEL_MAX_WIDTH,\n valueMaxWidth: base.valueMaxWidth ?? DEFAULT_VALUE_MAX_WIDTH,\n };\n}\n\n// JsonSchemaForm renders an object subschema as a form: one control per\n// (effective) property. It resolves if/then conditionals internally and recurses\n// through array items and object/map values; every other behaviour is layered on\n// by the pre/post extension functions the consumer supplies. The component holds\n// no domain knowledge.\nexport function JsonSchemaForm({\n schema,\n value,\n onChange,\n readOnly = false,\n inline = false,\n layout,\n size = DEFAULT_FORM_SIZE,\n idPrefix,\n hideReadOnlyFields = false,\n hiddenKeys,\n requiredFirst = false,\n title,\n pre,\n post,\n}: JsonSchemaFormProps) {\n const resolvedLayout = resolveFormLayout(layout, inline);\n const ctx: RenderContext = {\n readOnly,\n hideReadOnlyFields,\n layout: resolvedLayout,\n size,\n requiredFirst,\n pre: pre ?? [],\n post: post ?? [],\n depth: 0,\n ...(idPrefix ? { idPrefix } : {}),\n };\n const rows = renderObjectFields(schema, value, onChange, ctx, hiddenKeys ? { hiddenKeys } : undefined);\n\n const rowGap = resolvedLayout.mode === \"inline\" ? inlineRowGapClass[size] : stackedRowGapClass[size];\n return (\n <div className={cn(\"flex flex-col\", fieldInnerGapClass[size])}>\n {title && <h3 className={cn(\"font-semibold\", labelSizeClass[size])}>{title}</h3>}\n <div className={cn(\"grid\", rowGap)}>{rows}</div>\n </div>\n );\n}\n"],"names":[],"mappings":";;;;AAWA,MAAM,0BAA0B;AAChC,MAAM,0BAA0B;AAKhC,SAAS,kBAAkB,QAAgC,QAA6B;AACtF,QAAM,OAAO,UAAU,EAAE,MAAM,SAAS,WAAW,UAAA;AACnD,MAAI,KAAK,SAAS,SAAU,QAAO,EAAE,MAAM,UAAA;AAC3C,SAAO;AAAA,IACL,MAAM;AAAA,IACN,eAAe,KAAK,iBAAiB;AAAA,IACrC,eAAe,KAAK,iBAAiB;AAAA,EAAA;AAEzC;AAOO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,SAAS;AAAA,EACT;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,qBAAqB;AAAA,EACrB;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,QAAM,iBAAiB,kBAAkB,QAAQ,MAAM;AACvD,QAAM,MAAqB;AAAA,IACzB;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,KAAK,OAAO,CAAA;AAAA,IACZ,MAAM,QAAQ,CAAA;AAAA,IACd,OAAO;AAAA,IACP,GAAI,WAAW,EAAE,aAAa,CAAA;AAAA,EAAC;AAEjC,QAAM,OAAO,mBAAmB,QAAQ,OAAO,UAAU,KAAK,aAAa,EAAE,WAAA,IAAe,MAAS;AAErG,QAAM,SAAS,eAAe,SAAS,WAAW,kBAAkB,IAAI,IAAI,mBAAmB,IAAI;AACnG,SACE,qBAAC,SAAI,WAAW,GAAG,iBAAiB,mBAAmB,IAAI,CAAC,GACzD,UAAA;AAAA,IAAA,SAAS,oBAAC,QAAG,WAAW,GAAG,iBAAiB,eAAe,IAAI,CAAC,GAAI,UAAA,MAAA,CAAM;AAAA,wBAC1E,OAAA,EAAI,WAAW,GAAG,QAAQ,MAAM,GAAI,UAAA,KAAA,CAAK;AAAA,EAAA,GAC5C;AAEJ;"}
1
+ {"version":3,"file":"JsonSchemaForm.js","sources":["../../src/components/JsonSchemaForm.tsx"],"sourcesContent":["import { cn } from \"../lib/utils\";\nimport { renderObjectFields, type RenderContext } from \"./json-schema-form-render\";\nimport {\n DEFAULT_FORM_SIZE,\n fieldInnerGapClass,\n inlineRowGapClass,\n labelSizeClass,\n stackedRowGapClass,\n} from \"./json-schema-form-size\";\nimport type { FormLayout, JsonSchemaFormProps } from \"./json-schema-form-types\";\n\nconst DEFAULT_LABEL_MAX_WIDTH = \"40ch\";\nconst DEFAULT_VALUE_MAX_WIDTH = \"600px\";\n\n// resolveFormLayout maps the `layout`/`inline` props to a single resolved\n// FormLayout. An explicit `layout` wins; otherwise `inline` selects the inline\n// preset. Width caps are only filled for inline mode.\nfunction resolveFormLayout(layout: FormLayout | undefined, inline: boolean): FormLayout {\n const base = layout ?? { mode: inline ? \"inline\" : \"stacked\" };\n if (base.mode !== \"inline\") return { mode: \"stacked\" };\n return {\n mode: \"inline\",\n labelMaxWidth: base.labelMaxWidth ?? DEFAULT_LABEL_MAX_WIDTH,\n valueMaxWidth: base.valueMaxWidth ?? DEFAULT_VALUE_MAX_WIDTH,\n };\n}\n\n// JsonSchemaForm renders an object subschema as a form: one control per\n// (effective) property. It resolves if/then conditionals internally and recurses\n// through array items and object/map values; every other behaviour is layered on\n// by the pre/post extension functions the consumer supplies. The component holds\n// no domain knowledge.\nexport function JsonSchemaForm({\n schema,\n value,\n onChange,\n readOnly = false,\n inline = false,\n layout,\n size = DEFAULT_FORM_SIZE,\n idPrefix,\n hideReadOnlyFields = false,\n hiddenKeys,\n requiredFirst = false,\n title,\n pre,\n post,\n}: JsonSchemaFormProps) {\n const resolvedLayout = resolveFormLayout(layout, inline);\n const ctx: RenderContext = {\n readOnly,\n hideReadOnlyFields,\n layout: resolvedLayout,\n size,\n requiredFirst,\n pre: pre ?? [],\n post: post ?? [],\n depth: 0,\n ...(idPrefix ? { idPrefix } : {}),\n };\n const rows = renderObjectFields(schema, value, onChange, ctx, hiddenKeys ? { hiddenKeys } : undefined);\n\n const rowGap = resolvedLayout.mode === \"inline\" ? inlineRowGapClass[size] : stackedRowGapClass[size];\n return (\n <div className={cn(\"flex flex-col\", fieldInnerGapClass[size])}>\n {title && <h3 className={cn(\"font-semibold\", labelSizeClass[size])}>{title}</h3>}\n <div className={cn(\"grid\", rowGap)}>{rows}</div>\n </div>\n );\n}\n"],"names":[],"mappings":";;;;AAWA,MAAM,0BAA0B;AAChC,MAAM,0BAA0B;AAKhC,SAAS,kBAAkB,QAAgC,QAA6B;AACtF,QAAM,OAAO,UAAU,EAAE,MAAM,SAAS,WAAW,UAAA;AACnD,MAAI,KAAK,SAAS,SAAU,QAAO,EAAE,MAAM,UAAA;AAC3C,SAAO;AAAA,IACL,MAAM;AAAA,IACN,eAAe,KAAK,iBAAiB;AAAA,IACrC,eAAe,KAAK,iBAAiB;AAAA,EAAA;AAEzC;AAOO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,SAAS;AAAA,EACT;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,qBAAqB;AAAA,EACrB;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,QAAM,iBAAiB,kBAAkB,QAAQ,MAAM;AACvD,QAAM,MAAqB;AAAA,IACzB;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,KAAK,OAAO,CAAA;AAAA,IACZ,MAAM,QAAQ,CAAA;AAAA,IACd,OAAO;AAAA,IACP,GAAI,WAAW,EAAE,aAAa,CAAA;AAAA,EAAC;AAEjC,QAAM,OAAO,mBAAmB,QAAQ,OAAO,UAAU,KAAK,aAAa,EAAE,WAAA,IAAe,MAAS;AAErG,QAAM,SAAS,eAAe,SAAS,WAAW,kBAAkB,IAAI,IAAI,mBAAmB,IAAI;AACnG,SACE,qBAAC,SAAI,WAAW,GAAG,iBAAiB,mBAAmB,IAAI,CAAC,GACzD,UAAA;AAAA,IAAA,SAAS,oBAAC,QAAG,WAAW,GAAG,iBAAiB,eAAe,IAAI,CAAC,GAAI,UAAA,MAAA,CAAM;AAAA,wBAC1E,OAAA,EAAI,WAAW,GAAG,QAAQ,MAAM,GAAI,UAAA,KAAA,CAAK;AAAA,EAAA,GAC5C;AAEJ;"}
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const react = require("react");
5
+ const mi = require("@flanksource/icons/mi");
6
+ const utils = require("../lib/utils.cjs");
7
+ const Combobox = require("./Combobox.cjs");
8
+ const UiEdit = require("../icons/components/UiEdit.cjs");
9
+ function SecretKeySelector({
10
+ value,
11
+ onChange,
12
+ loadResources,
13
+ loadKeyPreview,
14
+ allowLiteral = false,
15
+ strict = false,
16
+ className
17
+ }) {
18
+ const source = (value == null ? void 0 : value.kind) ?? "secret";
19
+ const isLiteral = source === "value";
20
+ const refKind = isLiteral ? "secret" : source;
21
+ const selectedName = value && value.kind !== "value" ? value.name : "";
22
+ const selectedKey = value && value.kind !== "value" ? value.key : "";
23
+ const literalValue = value && value.kind === "value" ? value.value : "";
24
+ const [resources, setResources] = react.useState([]);
25
+ const [resourcesLoading, setResourcesLoading] = react.useState(false);
26
+ const [previews, setPreviews] = react.useState([]);
27
+ const [previewLoading, setPreviewLoading] = react.useState(false);
28
+ react.useEffect(() => {
29
+ if (isLiteral) return;
30
+ let cancelled = false;
31
+ setResourcesLoading(true);
32
+ loadResources(refKind).then((res) => !cancelled && setResources(res)).finally(() => !cancelled && setResourcesLoading(false));
33
+ return () => {
34
+ cancelled = true;
35
+ };
36
+ }, [loadResources, refKind, isLiteral]);
37
+ react.useEffect(() => {
38
+ if (!selectedName) {
39
+ setPreviews([]);
40
+ return;
41
+ }
42
+ let cancelled = false;
43
+ setPreviewLoading(true);
44
+ loadKeyPreview(refKind, selectedName).then((res) => !cancelled && setPreviews(res)).catch(() => !cancelled && setPreviews([])).finally(() => !cancelled && setPreviewLoading(false));
45
+ return () => {
46
+ cancelled = true;
47
+ };
48
+ }, [loadKeyPreview, refKind, selectedName]);
49
+ const nameOptions = react.useMemo(
50
+ () => resources.map((r) => ({ value: r.name, label: r.name })),
51
+ [resources]
52
+ );
53
+ const selectedResource = react.useMemo(
54
+ () => resources.find((r) => r.name === selectedName),
55
+ [resources, selectedName]
56
+ );
57
+ const keyOptions = react.useMemo(
58
+ () => buildKeyOptions((selectedResource == null ? void 0 : selectedResource.keys) ?? [], previews, selectedKey),
59
+ [selectedResource, selectedKey, previews]
60
+ );
61
+ const nameInvalid = strict && !!selectedName && !resourcesLoading && !selectedResource;
62
+ const keyInvalid = strict && !!selectedKey && !!selectedResource && !resourcesLoading && !(selectedResource.keys ?? []).includes(selectedKey);
63
+ const setSource = (next) => onChange(next === "value" ? { kind: "value", value: "" } : { kind: next, name: "", key: "" });
64
+ const setName = (name) => onChange(name ? { kind: refKind, name, key: selectedKey } : void 0);
65
+ const setKey = (key) => selectedName ? onChange({ kind: refKind, name: selectedName, key }) : void 0;
66
+ const setLiteral = (v) => onChange({ kind: "value", value: v });
67
+ const sources = allowLiteral ? ["secret", "configmap", "value"] : ["secret", "configmap"];
68
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("flex items-center gap-2", className), children: [
69
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 overflow-hidden rounded border border-border text-xs", children: sources.map((k) => {
70
+ const Glyph = SOURCE_ICON[k];
71
+ return /* @__PURE__ */ jsxRuntime.jsxs(
72
+ "button",
73
+ {
74
+ type: "button",
75
+ onClick: () => setSource(k),
76
+ className: utils.cn(
77
+ "flex items-center gap-1 whitespace-nowrap px-2 py-1",
78
+ source === k ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-accent"
79
+ ),
80
+ children: [
81
+ /* @__PURE__ */ jsxRuntime.jsx(Glyph, { className: "h-3.5 w-3.5" }),
82
+ SOURCE_LABEL[k]
83
+ ]
84
+ },
85
+ k
86
+ );
87
+ }) }),
88
+ isLiteral ? /* @__PURE__ */ jsxRuntime.jsx(
89
+ "input",
90
+ {
91
+ type: "text",
92
+ value: literalValue,
93
+ onChange: (e) => setLiteral(e.target.value),
94
+ placeholder: "Static value…",
95
+ className: utils.cn(
96
+ "h-control-h min-w-0 flex-1 rounded border border-input bg-background px-control-px text-sm",
97
+ "outline-none focus-visible:ring-2 focus-visible:ring-ring"
98
+ )
99
+ }
100
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
101
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-44 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
102
+ Combobox.Combobox,
103
+ {
104
+ options: nameOptions,
105
+ value: selectedName,
106
+ onChange: setName,
107
+ allowCustomValue: true,
108
+ loading: resourcesLoading,
109
+ invalid: nameInvalid,
110
+ placeholder: `Select ${refKind}…`
111
+ }
112
+ ) }),
113
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
114
+ Combobox.Combobox,
115
+ {
116
+ options: keyOptions,
117
+ value: selectedKey,
118
+ onChange: setKey,
119
+ allowCustomValue: true,
120
+ loading: previewLoading,
121
+ invalid: keyInvalid,
122
+ placeholder: selectedName ? "Key…" : "—"
123
+ }
124
+ ) })
125
+ ] })
126
+ ] });
127
+ }
128
+ const SOURCE_ICON = {
129
+ secret: mi.K8SSecret,
130
+ configmap: mi.K8SConfigmap,
131
+ value: UiEdit.UiEdit
132
+ };
133
+ const SOURCE_LABEL = {
134
+ secret: "Secret",
135
+ configmap: "ConfigMap",
136
+ value: "Value"
137
+ };
138
+ function buildKeyOptions(keys, previews, selectedKey) {
139
+ const masked = new Map(previews.map((p) => [p.key, p.value]));
140
+ const base = keys.map((k) => ({
141
+ value: k,
142
+ label: masked.has(k) ? `${k} — ${masked.get(k)}` : k
143
+ }));
144
+ if (selectedKey && !base.some((o) => o.value === selectedKey)) {
145
+ return [{ value: selectedKey, label: selectedKey }, ...base];
146
+ }
147
+ return base;
148
+ }
149
+ exports.SecretKeySelector = SecretKeySelector;
150
+ exports.buildKeyOptions = buildKeyOptions;
151
+ //# sourceMappingURL=SecretKeySelector.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecretKeySelector.cjs","sources":["../../src/components/SecretKeySelector.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { K8SSecret, K8SConfigmap } from \"@flanksource/icons/mi\";\nimport { cn } from \"../lib/utils\";\nimport { UiEdit } from \"../icons\";\nimport { Combobox, type ComboboxOption } from \"./Combobox\";\n\n// SecretKeySelector picks a Secret or ConfigMap and one of its keys, with a\n// mid-masked preview of every key's value so the operator can tell which key\n// holds the host vs the db vs a password. With `allowLiteral` it also offers a\n// \"Value\" mode for typing a static inline string. It is presentational: it\n// fetches nothing — the consumer supplies async `loadResources` /\n// `loadKeyPreview` getters. It emits a discriminated value the consumer lowers\n// into whatever reference shape it needs.\n\nexport type SecretKind = \"secret\" | \"configmap\";\n\n/** Toggle options: the two resource kinds plus the inline-literal mode. */\nexport type SecretValueSource = SecretKind | \"value\";\n\n/** One key's mid-masked preview. `value` is already masked by the consumer. */\nexport type KeyPreview = { key: string; value: string };\n\n/**\n * The selector's value: either a {kind,name,key} reference into a Secret /\n * ConfigMap, or a static inline literal ({kind:\"value\", value}).\n */\nexport type SecretKeyValue =\n | { kind: SecretKind; name: string; key: string }\n | { kind: \"value\"; value: string };\n\n/** A named secret/configmap and its data key names (values never returned). */\nexport type SecretResource = { name: string; keys?: string[] };\n\nexport type SecretKeySelectorProps = {\n value: SecretKeyValue | undefined;\n onChange: (next: SecretKeyValue | undefined) => void;\n /** Loads the named kind's resources (name + data key names). */\n loadResources: (kind: SecretKind) => Promise<SecretResource[]>;\n /** Loads mid-masked previews for the named resource's keys. */\n loadKeyPreview: (kind: SecretKind, name: string) => Promise<KeyPreview[]>;\n /** Offer a third \"Value\" toggle for typing a static inline literal. */\n allowLiteral?: boolean;\n /**\n * When true, a chosen secret/configmap name absent from the loaded resources,\n * or a key absent from the chosen resource's keys, is flagged invalid (once\n * the respective load settles). The reference doesn't exist in the namespace.\n * The literal \"Value\" mode is never strict-flagged.\n */\n strict?: boolean;\n className?: string;\n};\n\nexport function SecretKeySelector({\n value,\n onChange,\n loadResources,\n loadKeyPreview,\n allowLiteral = false,\n strict = false,\n className,\n}: SecretKeySelectorProps) {\n const source: SecretValueSource = value?.kind ?? \"secret\";\n const isLiteral = source === \"value\";\n // A literal value carries no resource kind; the loaders default to \"secret\".\n const refKind: SecretKind = isLiteral ? \"secret\" : source;\n const selectedName = value && value.kind !== \"value\" ? value.name : \"\";\n const selectedKey = value && value.kind !== \"value\" ? value.key : \"\";\n const literalValue = value && value.kind === \"value\" ? value.value : \"\";\n\n const [resources, setResources] = useState<SecretResource[]>([]);\n const [resourcesLoading, setResourcesLoading] = useState(false);\n const [previews, setPreviews] = useState<KeyPreview[]>([]);\n const [previewLoading, setPreviewLoading] = useState(false);\n\n useEffect(() => {\n // Literal mode references no resource, so skip the list fetch entirely.\n if (isLiteral) return;\n let cancelled = false;\n setResourcesLoading(true);\n loadResources(refKind)\n .then((res) => !cancelled && setResources(res))\n .finally(() => !cancelled && setResourcesLoading(false));\n return () => {\n cancelled = true;\n };\n }, [loadResources, refKind, isLiteral]);\n\n useEffect(() => {\n if (!selectedName) {\n setPreviews([]);\n return;\n }\n let cancelled = false;\n setPreviewLoading(true);\n loadKeyPreview(refKind, selectedName)\n .then((res) => !cancelled && setPreviews(res))\n .catch(() => !cancelled && setPreviews([]))\n .finally(() => !cancelled && setPreviewLoading(false));\n return () => {\n cancelled = true;\n };\n }, [loadKeyPreview, refKind, selectedName]);\n\n const nameOptions = useMemo<ComboboxOption[]>(\n () => resources.map((r) => ({ value: r.name, label: r.name })),\n [resources],\n );\n const selectedResource = useMemo(\n () => resources.find((r) => r.name === selectedName),\n [resources, selectedName],\n );\n const keyOptions = useMemo(\n () => buildKeyOptions(selectedResource?.keys ?? [], previews, selectedKey),\n [selectedResource, selectedKey, previews],\n );\n\n // Strict validity: the name is invalid when it names no loaded resource; the\n // key is invalid when, with a resolved resource, it isn't one of that\n // resource's keys. The key list comes from the resource list (not previews),\n // so both are gated on resourcesLoading only.\n const nameInvalid = strict && !!selectedName && !resourcesLoading && !selectedResource;\n const keyInvalid =\n strict &&\n !!selectedKey &&\n !!selectedResource &&\n !resourcesLoading &&\n !(selectedResource.keys ?? []).includes(selectedKey);\n\n // Switching source resets the selection: a resource kind clears name+key; the\n // literal mode seeds an empty string (so onChange emits a value-kind value).\n const setSource = (next: SecretValueSource) =>\n onChange(next === \"value\" ? { kind: \"value\", value: \"\" } : { kind: next, name: \"\", key: \"\" });\n const setName = (name: string) =>\n onChange(name ? { kind: refKind, name, key: selectedKey } : undefined);\n const setKey = (key: string) =>\n selectedName ? onChange({ kind: refKind, name: selectedName, key }) : undefined;\n const setLiteral = (v: string) => onChange({ kind: \"value\", value: v });\n\n const sources: SecretValueSource[] = allowLiteral\n ? [\"secret\", \"configmap\", \"value\"]\n : [\"secret\", \"configmap\"];\n\n return (\n <div className={cn(\"flex items-center gap-2\", className)}>\n <div className=\"flex shrink-0 overflow-hidden rounded border border-border text-xs\">\n {sources.map((k) => {\n const Glyph = SOURCE_ICON[k];\n return (\n <button\n key={k}\n type=\"button\"\n onClick={() => setSource(k)}\n className={cn(\n \"flex items-center gap-1 whitespace-nowrap px-2 py-1\",\n source === k\n ? \"bg-primary text-primary-foreground\"\n : \"bg-muted text-muted-foreground hover:bg-accent\",\n )}\n >\n <Glyph className=\"h-3.5 w-3.5\" />\n {SOURCE_LABEL[k]}\n </button>\n );\n })}\n </div>\n {isLiteral ? (\n <input\n type=\"text\"\n value={literalValue}\n onChange={(e) => setLiteral(e.target.value)}\n placeholder=\"Static value…\"\n className={cn(\n \"h-control-h min-w-0 flex-1 rounded border border-input bg-background px-control-px text-sm\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n )}\n />\n ) : (\n <>\n <div className=\"w-44 shrink-0\">\n <Combobox\n options={nameOptions}\n value={selectedName}\n onChange={setName}\n allowCustomValue\n loading={resourcesLoading}\n invalid={nameInvalid}\n placeholder={`Select ${refKind}…`}\n />\n </div>\n <div className=\"min-w-0 flex-1\">\n <Combobox\n options={keyOptions}\n value={selectedKey}\n onChange={setKey}\n allowCustomValue\n loading={previewLoading}\n invalid={keyInvalid}\n placeholder={selectedName ? \"Key…\" : \"—\"}\n />\n </div>\n </>\n )}\n </div>\n );\n}\n\n// Icons share a className-only call shape; the flanksource FCs carry extra\n// static metadata that doesn't unify with ComponentType, so type the slot as a\n// plain render function instead.\nconst SOURCE_ICON: Record<SecretValueSource, (p: { className?: string }) => ReactNode> = {\n secret: K8SSecret,\n configmap: K8SConfigmap,\n value: UiEdit,\n};\n\nconst SOURCE_LABEL: Record<SecretValueSource, string> = {\n secret: \"Secret\",\n configmap: \"ConfigMap\",\n value: \"Value\",\n};\n\n// buildKeyOptions labels each key with its mid-masked preview value when one is\n// available. A selected key absent from the list is pinned so the selection\n// stays visible.\nexport function buildKeyOptions(\n keys: string[],\n previews: KeyPreview[],\n selectedKey: string,\n): ComboboxOption[] {\n const masked = new Map(previews.map((p) => [p.key, p.value]));\n const base = keys.map((k) => ({\n value: k,\n label: masked.has(k) ? `${k} — ${masked.get(k)}` : k,\n }));\n if (selectedKey && !base.some((o) => o.value === selectedKey)) {\n return [{ value: selectedKey, label: selectedKey }, ...base];\n }\n return base;\n}\n"],"names":["useState","useEffect","useMemo","cn","jsx","jsxs","Fragment","Combobox","K8SSecret","K8SConfigmap","UiEdit"],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { ComboboxOption } from './Combobox';
2
+ export type SecretKind = "secret" | "configmap";
3
+ /** Toggle options: the two resource kinds plus the inline-literal mode. */
4
+ export type SecretValueSource = SecretKind | "value";
5
+ /** One key's mid-masked preview. `value` is already masked by the consumer. */
6
+ export type KeyPreview = {
7
+ key: string;
8
+ value: string;
9
+ };
10
+ /**
11
+ * The selector's value: either a {kind,name,key} reference into a Secret /
12
+ * ConfigMap, or a static inline literal ({kind:"value", value}).
13
+ */
14
+ export type SecretKeyValue = {
15
+ kind: SecretKind;
16
+ name: string;
17
+ key: string;
18
+ } | {
19
+ kind: "value";
20
+ value: string;
21
+ };
22
+ /** A named secret/configmap and its data key names (values never returned). */
23
+ export type SecretResource = {
24
+ name: string;
25
+ keys?: string[];
26
+ };
27
+ export type SecretKeySelectorProps = {
28
+ value: SecretKeyValue | undefined;
29
+ onChange: (next: SecretKeyValue | undefined) => void;
30
+ /** Loads the named kind's resources (name + data key names). */
31
+ loadResources: (kind: SecretKind) => Promise<SecretResource[]>;
32
+ /** Loads mid-masked previews for the named resource's keys. */
33
+ loadKeyPreview: (kind: SecretKind, name: string) => Promise<KeyPreview[]>;
34
+ /** Offer a third "Value" toggle for typing a static inline literal. */
35
+ allowLiteral?: boolean;
36
+ /**
37
+ * When true, a chosen secret/configmap name absent from the loaded resources,
38
+ * or a key absent from the chosen resource's keys, is flagged invalid (once
39
+ * the respective load settles). The reference doesn't exist in the namespace.
40
+ * The literal "Value" mode is never strict-flagged.
41
+ */
42
+ strict?: boolean;
43
+ className?: string;
44
+ };
45
+ export declare function SecretKeySelector({ value, onChange, loadResources, loadKeyPreview, allowLiteral, strict, className, }: SecretKeySelectorProps): import("react/jsx-runtime").JSX.Element;
46
+ export declare function buildKeyOptions(keys: string[], previews: KeyPreview[], selectedKey: string): ComboboxOption[];
47
+ //# sourceMappingURL=SecretKeySelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecretKeySelector.d.ts","sourceRoot":"","sources":["../../src/components/SecretKeySelector.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAU3D,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEhD,2EAA2E;AAC3E,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,OAAO,CAAC;AAErD,+EAA+E;AAC/E,MAAM,MAAM,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAErC,+EAA+E;AAC/E,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE/D,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,EAAE,cAAc,GAAG,SAAS,CAAC;IAClC,QAAQ,EAAE,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,KAAK,IAAI,CAAC;IACrD,gEAAgE;IAChE,aAAa,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/D,+DAA+D;IAC/D,cAAc,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC1E,uEAAuE;IACvE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,aAAa,EACb,cAAc,EACd,YAAoB,EACpB,MAAc,EACd,SAAS,GACV,EAAE,sBAAsB,2CAgJxB;AAoBD,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EAAE,EACd,QAAQ,EAAE,UAAU,EAAE,EACtB,WAAW,EAAE,MAAM,GAClB,cAAc,EAAE,CAUlB"}
@@ -0,0 +1,151 @@
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect, useMemo } from "react";
3
+ import { K8SConfigmap, K8SSecret } from "@flanksource/icons/mi";
4
+ import { cn } from "../lib/utils.js";
5
+ import { Combobox } from "./Combobox.js";
6
+ import { UiEdit } from "../icons/components/UiEdit.js";
7
+ function SecretKeySelector({
8
+ value,
9
+ onChange,
10
+ loadResources,
11
+ loadKeyPreview,
12
+ allowLiteral = false,
13
+ strict = false,
14
+ className
15
+ }) {
16
+ const source = (value == null ? void 0 : value.kind) ?? "secret";
17
+ const isLiteral = source === "value";
18
+ const refKind = isLiteral ? "secret" : source;
19
+ const selectedName = value && value.kind !== "value" ? value.name : "";
20
+ const selectedKey = value && value.kind !== "value" ? value.key : "";
21
+ const literalValue = value && value.kind === "value" ? value.value : "";
22
+ const [resources, setResources] = useState([]);
23
+ const [resourcesLoading, setResourcesLoading] = useState(false);
24
+ const [previews, setPreviews] = useState([]);
25
+ const [previewLoading, setPreviewLoading] = useState(false);
26
+ useEffect(() => {
27
+ if (isLiteral) return;
28
+ let cancelled = false;
29
+ setResourcesLoading(true);
30
+ loadResources(refKind).then((res) => !cancelled && setResources(res)).finally(() => !cancelled && setResourcesLoading(false));
31
+ return () => {
32
+ cancelled = true;
33
+ };
34
+ }, [loadResources, refKind, isLiteral]);
35
+ useEffect(() => {
36
+ if (!selectedName) {
37
+ setPreviews([]);
38
+ return;
39
+ }
40
+ let cancelled = false;
41
+ setPreviewLoading(true);
42
+ loadKeyPreview(refKind, selectedName).then((res) => !cancelled && setPreviews(res)).catch(() => !cancelled && setPreviews([])).finally(() => !cancelled && setPreviewLoading(false));
43
+ return () => {
44
+ cancelled = true;
45
+ };
46
+ }, [loadKeyPreview, refKind, selectedName]);
47
+ const nameOptions = useMemo(
48
+ () => resources.map((r) => ({ value: r.name, label: r.name })),
49
+ [resources]
50
+ );
51
+ const selectedResource = useMemo(
52
+ () => resources.find((r) => r.name === selectedName),
53
+ [resources, selectedName]
54
+ );
55
+ const keyOptions = useMemo(
56
+ () => buildKeyOptions((selectedResource == null ? void 0 : selectedResource.keys) ?? [], previews, selectedKey),
57
+ [selectedResource, selectedKey, previews]
58
+ );
59
+ const nameInvalid = strict && !!selectedName && !resourcesLoading && !selectedResource;
60
+ const keyInvalid = strict && !!selectedKey && !!selectedResource && !resourcesLoading && !(selectedResource.keys ?? []).includes(selectedKey);
61
+ const setSource = (next) => onChange(next === "value" ? { kind: "value", value: "" } : { kind: next, name: "", key: "" });
62
+ const setName = (name) => onChange(name ? { kind: refKind, name, key: selectedKey } : void 0);
63
+ const setKey = (key) => selectedName ? onChange({ kind: refKind, name: selectedName, key }) : void 0;
64
+ const setLiteral = (v) => onChange({ kind: "value", value: v });
65
+ const sources = allowLiteral ? ["secret", "configmap", "value"] : ["secret", "configmap"];
66
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2", className), children: [
67
+ /* @__PURE__ */ jsx("div", { className: "flex shrink-0 overflow-hidden rounded border border-border text-xs", children: sources.map((k) => {
68
+ const Glyph = SOURCE_ICON[k];
69
+ return /* @__PURE__ */ jsxs(
70
+ "button",
71
+ {
72
+ type: "button",
73
+ onClick: () => setSource(k),
74
+ className: cn(
75
+ "flex items-center gap-1 whitespace-nowrap px-2 py-1",
76
+ source === k ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-accent"
77
+ ),
78
+ children: [
79
+ /* @__PURE__ */ jsx(Glyph, { className: "h-3.5 w-3.5" }),
80
+ SOURCE_LABEL[k]
81
+ ]
82
+ },
83
+ k
84
+ );
85
+ }) }),
86
+ isLiteral ? /* @__PURE__ */ jsx(
87
+ "input",
88
+ {
89
+ type: "text",
90
+ value: literalValue,
91
+ onChange: (e) => setLiteral(e.target.value),
92
+ placeholder: "Static value…",
93
+ className: cn(
94
+ "h-control-h min-w-0 flex-1 rounded border border-input bg-background px-control-px text-sm",
95
+ "outline-none focus-visible:ring-2 focus-visible:ring-ring"
96
+ )
97
+ }
98
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
99
+ /* @__PURE__ */ jsx("div", { className: "w-44 shrink-0", children: /* @__PURE__ */ jsx(
100
+ Combobox,
101
+ {
102
+ options: nameOptions,
103
+ value: selectedName,
104
+ onChange: setName,
105
+ allowCustomValue: true,
106
+ loading: resourcesLoading,
107
+ invalid: nameInvalid,
108
+ placeholder: `Select ${refKind}…`
109
+ }
110
+ ) }),
111
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
112
+ Combobox,
113
+ {
114
+ options: keyOptions,
115
+ value: selectedKey,
116
+ onChange: setKey,
117
+ allowCustomValue: true,
118
+ loading: previewLoading,
119
+ invalid: keyInvalid,
120
+ placeholder: selectedName ? "Key…" : "—"
121
+ }
122
+ ) })
123
+ ] })
124
+ ] });
125
+ }
126
+ const SOURCE_ICON = {
127
+ secret: K8SSecret,
128
+ configmap: K8SConfigmap,
129
+ value: UiEdit
130
+ };
131
+ const SOURCE_LABEL = {
132
+ secret: "Secret",
133
+ configmap: "ConfigMap",
134
+ value: "Value"
135
+ };
136
+ function buildKeyOptions(keys, previews, selectedKey) {
137
+ const masked = new Map(previews.map((p) => [p.key, p.value]));
138
+ const base = keys.map((k) => ({
139
+ value: k,
140
+ label: masked.has(k) ? `${k} — ${masked.get(k)}` : k
141
+ }));
142
+ if (selectedKey && !base.some((o) => o.value === selectedKey)) {
143
+ return [{ value: selectedKey, label: selectedKey }, ...base];
144
+ }
145
+ return base;
146
+ }
147
+ export {
148
+ SecretKeySelector,
149
+ buildKeyOptions
150
+ };
151
+ //# sourceMappingURL=SecretKeySelector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecretKeySelector.js","sources":["../../src/components/SecretKeySelector.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { K8SSecret, K8SConfigmap } from \"@flanksource/icons/mi\";\nimport { cn } from \"../lib/utils\";\nimport { UiEdit } from \"../icons\";\nimport { Combobox, type ComboboxOption } from \"./Combobox\";\n\n// SecretKeySelector picks a Secret or ConfigMap and one of its keys, with a\n// mid-masked preview of every key's value so the operator can tell which key\n// holds the host vs the db vs a password. With `allowLiteral` it also offers a\n// \"Value\" mode for typing a static inline string. It is presentational: it\n// fetches nothing — the consumer supplies async `loadResources` /\n// `loadKeyPreview` getters. It emits a discriminated value the consumer lowers\n// into whatever reference shape it needs.\n\nexport type SecretKind = \"secret\" | \"configmap\";\n\n/** Toggle options: the two resource kinds plus the inline-literal mode. */\nexport type SecretValueSource = SecretKind | \"value\";\n\n/** One key's mid-masked preview. `value` is already masked by the consumer. */\nexport type KeyPreview = { key: string; value: string };\n\n/**\n * The selector's value: either a {kind,name,key} reference into a Secret /\n * ConfigMap, or a static inline literal ({kind:\"value\", value}).\n */\nexport type SecretKeyValue =\n | { kind: SecretKind; name: string; key: string }\n | { kind: \"value\"; value: string };\n\n/** A named secret/configmap and its data key names (values never returned). */\nexport type SecretResource = { name: string; keys?: string[] };\n\nexport type SecretKeySelectorProps = {\n value: SecretKeyValue | undefined;\n onChange: (next: SecretKeyValue | undefined) => void;\n /** Loads the named kind's resources (name + data key names). */\n loadResources: (kind: SecretKind) => Promise<SecretResource[]>;\n /** Loads mid-masked previews for the named resource's keys. */\n loadKeyPreview: (kind: SecretKind, name: string) => Promise<KeyPreview[]>;\n /** Offer a third \"Value\" toggle for typing a static inline literal. */\n allowLiteral?: boolean;\n /**\n * When true, a chosen secret/configmap name absent from the loaded resources,\n * or a key absent from the chosen resource's keys, is flagged invalid (once\n * the respective load settles). The reference doesn't exist in the namespace.\n * The literal \"Value\" mode is never strict-flagged.\n */\n strict?: boolean;\n className?: string;\n};\n\nexport function SecretKeySelector({\n value,\n onChange,\n loadResources,\n loadKeyPreview,\n allowLiteral = false,\n strict = false,\n className,\n}: SecretKeySelectorProps) {\n const source: SecretValueSource = value?.kind ?? \"secret\";\n const isLiteral = source === \"value\";\n // A literal value carries no resource kind; the loaders default to \"secret\".\n const refKind: SecretKind = isLiteral ? \"secret\" : source;\n const selectedName = value && value.kind !== \"value\" ? value.name : \"\";\n const selectedKey = value && value.kind !== \"value\" ? value.key : \"\";\n const literalValue = value && value.kind === \"value\" ? value.value : \"\";\n\n const [resources, setResources] = useState<SecretResource[]>([]);\n const [resourcesLoading, setResourcesLoading] = useState(false);\n const [previews, setPreviews] = useState<KeyPreview[]>([]);\n const [previewLoading, setPreviewLoading] = useState(false);\n\n useEffect(() => {\n // Literal mode references no resource, so skip the list fetch entirely.\n if (isLiteral) return;\n let cancelled = false;\n setResourcesLoading(true);\n loadResources(refKind)\n .then((res) => !cancelled && setResources(res))\n .finally(() => !cancelled && setResourcesLoading(false));\n return () => {\n cancelled = true;\n };\n }, [loadResources, refKind, isLiteral]);\n\n useEffect(() => {\n if (!selectedName) {\n setPreviews([]);\n return;\n }\n let cancelled = false;\n setPreviewLoading(true);\n loadKeyPreview(refKind, selectedName)\n .then((res) => !cancelled && setPreviews(res))\n .catch(() => !cancelled && setPreviews([]))\n .finally(() => !cancelled && setPreviewLoading(false));\n return () => {\n cancelled = true;\n };\n }, [loadKeyPreview, refKind, selectedName]);\n\n const nameOptions = useMemo<ComboboxOption[]>(\n () => resources.map((r) => ({ value: r.name, label: r.name })),\n [resources],\n );\n const selectedResource = useMemo(\n () => resources.find((r) => r.name === selectedName),\n [resources, selectedName],\n );\n const keyOptions = useMemo(\n () => buildKeyOptions(selectedResource?.keys ?? [], previews, selectedKey),\n [selectedResource, selectedKey, previews],\n );\n\n // Strict validity: the name is invalid when it names no loaded resource; the\n // key is invalid when, with a resolved resource, it isn't one of that\n // resource's keys. The key list comes from the resource list (not previews),\n // so both are gated on resourcesLoading only.\n const nameInvalid = strict && !!selectedName && !resourcesLoading && !selectedResource;\n const keyInvalid =\n strict &&\n !!selectedKey &&\n !!selectedResource &&\n !resourcesLoading &&\n !(selectedResource.keys ?? []).includes(selectedKey);\n\n // Switching source resets the selection: a resource kind clears name+key; the\n // literal mode seeds an empty string (so onChange emits a value-kind value).\n const setSource = (next: SecretValueSource) =>\n onChange(next === \"value\" ? { kind: \"value\", value: \"\" } : { kind: next, name: \"\", key: \"\" });\n const setName = (name: string) =>\n onChange(name ? { kind: refKind, name, key: selectedKey } : undefined);\n const setKey = (key: string) =>\n selectedName ? onChange({ kind: refKind, name: selectedName, key }) : undefined;\n const setLiteral = (v: string) => onChange({ kind: \"value\", value: v });\n\n const sources: SecretValueSource[] = allowLiteral\n ? [\"secret\", \"configmap\", \"value\"]\n : [\"secret\", \"configmap\"];\n\n return (\n <div className={cn(\"flex items-center gap-2\", className)}>\n <div className=\"flex shrink-0 overflow-hidden rounded border border-border text-xs\">\n {sources.map((k) => {\n const Glyph = SOURCE_ICON[k];\n return (\n <button\n key={k}\n type=\"button\"\n onClick={() => setSource(k)}\n className={cn(\n \"flex items-center gap-1 whitespace-nowrap px-2 py-1\",\n source === k\n ? \"bg-primary text-primary-foreground\"\n : \"bg-muted text-muted-foreground hover:bg-accent\",\n )}\n >\n <Glyph className=\"h-3.5 w-3.5\" />\n {SOURCE_LABEL[k]}\n </button>\n );\n })}\n </div>\n {isLiteral ? (\n <input\n type=\"text\"\n value={literalValue}\n onChange={(e) => setLiteral(e.target.value)}\n placeholder=\"Static value…\"\n className={cn(\n \"h-control-h min-w-0 flex-1 rounded border border-input bg-background px-control-px text-sm\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n )}\n />\n ) : (\n <>\n <div className=\"w-44 shrink-0\">\n <Combobox\n options={nameOptions}\n value={selectedName}\n onChange={setName}\n allowCustomValue\n loading={resourcesLoading}\n invalid={nameInvalid}\n placeholder={`Select ${refKind}…`}\n />\n </div>\n <div className=\"min-w-0 flex-1\">\n <Combobox\n options={keyOptions}\n value={selectedKey}\n onChange={setKey}\n allowCustomValue\n loading={previewLoading}\n invalid={keyInvalid}\n placeholder={selectedName ? \"Key…\" : \"—\"}\n />\n </div>\n </>\n )}\n </div>\n );\n}\n\n// Icons share a className-only call shape; the flanksource FCs carry extra\n// static metadata that doesn't unify with ComponentType, so type the slot as a\n// plain render function instead.\nconst SOURCE_ICON: Record<SecretValueSource, (p: { className?: string }) => ReactNode> = {\n secret: K8SSecret,\n configmap: K8SConfigmap,\n value: UiEdit,\n};\n\nconst SOURCE_LABEL: Record<SecretValueSource, string> = {\n secret: \"Secret\",\n configmap: \"ConfigMap\",\n value: \"Value\",\n};\n\n// buildKeyOptions labels each key with its mid-masked preview value when one is\n// available. A selected key absent from the list is pinned so the selection\n// stays visible.\nexport function buildKeyOptions(\n keys: string[],\n previews: KeyPreview[],\n selectedKey: string,\n): ComboboxOption[] {\n const masked = new Map(previews.map((p) => [p.key, p.value]));\n const base = keys.map((k) => ({\n value: k,\n label: masked.has(k) ? `${k} — ${masked.get(k)}` : k,\n }));\n if (selectedKey && !base.some((o) => o.value === selectedKey)) {\n return [{ value: selectedKey, label: selectedKey }, ...base];\n }\n return base;\n}\n"],"names":[],"mappings":"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"}