@qoretechnologies/reqraft 0.10.25 → 0.10.26

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 (310) hide show
  1. package/.claude/CLAUDE.md +52 -23
  2. package/design/COMPACT_ENGINE_REDESIGN.md +156 -0
  3. package/design/FORM_ENGINE_COMPACT_UX_PLAN.md +353 -0
  4. package/dist/components/FocusedEditing/index.d.ts +9 -1
  5. package/dist/components/FocusedEditing/index.d.ts.map +1 -1
  6. package/dist/components/FocusedEditing/index.js +2 -2
  7. package/dist/components/FocusedEditing/index.js.map +1 -1
  8. package/dist/components/composer/Composer.d.ts +108 -0
  9. package/dist/components/composer/Composer.d.ts.map +1 -0
  10. package/dist/components/composer/Composer.js +238 -0
  11. package/dist/components/composer/Composer.js.map +1 -0
  12. package/dist/components/composer/helpers.d.ts +21 -0
  13. package/dist/components/composer/helpers.d.ts.map +1 -0
  14. package/dist/components/composer/helpers.js +75 -0
  15. package/dist/components/composer/helpers.js.map +1 -0
  16. package/dist/components/composer/index.d.ts +3 -0
  17. package/dist/components/composer/index.d.ts.map +1 -0
  18. package/dist/components/composer/index.js +19 -0
  19. package/dist/components/composer/index.js.map +1 -0
  20. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  21. package/dist/components/form/engine/CompactRow.js +307 -110
  22. package/dist/components/form/engine/CompactRow.js.map +1 -1
  23. package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
  24. package/dist/components/form/engine/CompactToolbar.js +123 -106
  25. package/dist/components/form/engine/CompactToolbar.js.map +1 -1
  26. package/dist/components/form/engine/FormEngine.d.ts +107 -11
  27. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  28. package/dist/components/form/engine/FormEngine.js +618 -179
  29. package/dist/components/form/engine/FormEngine.js.map +1 -1
  30. package/dist/components/form/engine/compactRowContext.d.ts +9 -0
  31. package/dist/components/form/engine/compactRowContext.d.ts.map +1 -1
  32. package/dist/components/form/engine/compactRowContext.js.map +1 -1
  33. package/dist/components/form/engine/compactRowStyles.d.ts +7 -3
  34. package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
  35. package/dist/components/form/engine/compactRowStyles.js +88 -51
  36. package/dist/components/form/engine/compactRowStyles.js.map +1 -1
  37. package/dist/components/form/engine/compactToolbarContext.d.ts +1 -0
  38. package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
  39. package/dist/components/form/engine/compactToolbarContext.js.map +1 -1
  40. package/dist/components/form/engine/optionActions.d.ts +28 -0
  41. package/dist/components/form/engine/optionActions.d.ts.map +1 -0
  42. package/dist/components/form/engine/optionActions.js +18 -0
  43. package/dist/components/form/engine/optionActions.js.map +1 -0
  44. package/dist/components/form/engine/readFirst.d.ts +63 -2
  45. package/dist/components/form/engine/readFirst.d.ts.map +1 -1
  46. package/dist/components/form/engine/readFirst.js +121 -6
  47. package/dist/components/form/engine/readFirst.js.map +1 -1
  48. package/dist/components/form/engine/rendererTypes.d.ts +35 -0
  49. package/dist/components/form/engine/rendererTypes.d.ts.map +1 -0
  50. package/dist/components/form/engine/rendererTypes.js +63 -0
  51. package/dist/components/form/engine/rendererTypes.js.map +1 -0
  52. package/dist/components/form/engine/variants/VariantCalmTable.d.ts +6 -0
  53. package/dist/components/form/engine/variants/VariantCalmTable.d.ts.map +1 -0
  54. package/dist/components/form/engine/variants/VariantCalmTable.js +94 -0
  55. package/dist/components/form/engine/variants/VariantCalmTable.js.map +1 -0
  56. package/dist/components/form/engine/variants/VariantCards.d.ts +6 -0
  57. package/dist/components/form/engine/variants/VariantCards.d.ts.map +1 -0
  58. package/dist/components/form/engine/variants/VariantCards.js +80 -0
  59. package/dist/components/form/engine/variants/VariantCards.js.map +1 -0
  60. package/dist/components/form/engine/variants/VariantFocus.d.ts +7 -0
  61. package/dist/components/form/engine/variants/VariantFocus.d.ts.map +1 -0
  62. package/dist/components/form/engine/variants/VariantFocus.js +138 -0
  63. package/dist/components/form/engine/variants/VariantFocus.js.map +1 -0
  64. package/dist/components/form/engine/variants/VariantMinimal.d.ts +6 -0
  65. package/dist/components/form/engine/variants/VariantMinimal.d.ts.map +1 -0
  66. package/dist/components/form/engine/variants/VariantMinimal.js +73 -0
  67. package/dist/components/form/engine/variants/VariantMinimal.js.map +1 -0
  68. package/dist/components/form/engine/variants/focusDemo.d.ts +13 -0
  69. package/dist/components/form/engine/variants/focusDemo.d.ts.map +1 -0
  70. package/dist/components/form/engine/variants/focusDemo.js +139 -0
  71. package/dist/components/form/engine/variants/focusDemo.js.map +1 -0
  72. package/dist/components/form/engine/variants/variantModel.d.ts +70 -0
  73. package/dist/components/form/engine/variants/variantModel.d.ts.map +1 -0
  74. package/dist/components/form/engine/variants/variantModel.js +133 -0
  75. package/dist/components/form/engine/variants/variantModel.js.map +1 -0
  76. package/dist/components/form/engine/variants/variantParts.d.ts +79 -0
  77. package/dist/components/form/engine/variants/variantParts.d.ts.map +1 -0
  78. package/dist/components/form/engine/variants/variantParts.js +191 -0
  79. package/dist/components/form/engine/variants/variantParts.js.map +1 -0
  80. package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
  81. package/dist/components/form/expressions/builder/index.js +13 -2
  82. package/dist/components/form/expressions/builder/index.js.map +1 -1
  83. package/dist/components/form/expressions/useExpressions.d.ts.map +1 -1
  84. package/dist/components/form/expressions/useExpressions.js +4 -1
  85. package/dist/components/form/expressions/useExpressions.js.map +1 -1
  86. package/dist/components/form/fields/Field.d.ts.map +1 -1
  87. package/dist/components/form/fields/Field.js +5 -3
  88. package/dist/components/form/fields/Field.js.map +1 -1
  89. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +2 -1
  90. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
  91. package/dist/components/form/fields/allowed-values/AllowedValues.js +8 -26
  92. package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
  93. package/dist/components/form/fields/auto/AutoFormField.d.ts +3 -0
  94. package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
  95. package/dist/components/form/fields/auto/AutoFormField.js +42 -8
  96. package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
  97. package/dist/components/form/fields/binary/Binary.d.ts +15 -0
  98. package/dist/components/form/fields/binary/Binary.d.ts.map +1 -0
  99. package/dist/components/form/fields/binary/Binary.js +74 -0
  100. package/dist/components/form/fields/binary/Binary.js.map +1 -0
  101. package/dist/components/form/fields/boolean/Boolean.js +1 -1
  102. package/dist/components/form/fields/boolean/Boolean.js.map +1 -1
  103. package/dist/components/form/fields/multi-select/MultiSelectFormField.d.ts.map +1 -1
  104. package/dist/components/form/fields/multi-select/MultiSelectFormField.js +5 -4
  105. package/dist/components/form/fields/multi-select/MultiSelectFormField.js.map +1 -1
  106. package/dist/components/form/fields/select/Select.d.ts.map +1 -1
  107. package/dist/components/form/fields/select/Select.js +29 -12
  108. package/dist/components/form/fields/select/Select.js.map +1 -1
  109. package/dist/components/form/fields/select/SelectCollection.d.ts +8 -2
  110. package/dist/components/form/fields/select/SelectCollection.d.ts.map +1 -1
  111. package/dist/components/form/fields/select/SelectCollection.js +52 -16
  112. package/dist/components/form/fields/select/SelectCollection.js.map +1 -1
  113. package/dist/components/form/index.d.ts +2 -0
  114. package/dist/components/form/index.d.ts.map +1 -1
  115. package/dist/components/form/index.js +2 -0
  116. package/dist/components/form/index.js.map +1 -1
  117. package/dist/components/imageLightbox/ImageLightbox.d.ts +21 -0
  118. package/dist/components/imageLightbox/ImageLightbox.d.ts.map +1 -0
  119. package/dist/components/imageLightbox/ImageLightbox.js +169 -0
  120. package/dist/components/imageLightbox/ImageLightbox.js.map +1 -0
  121. package/dist/components/imageLightbox/index.d.ts +2 -0
  122. package/dist/components/imageLightbox/index.d.ts.map +1 -0
  123. package/dist/components/imageLightbox/index.js +18 -0
  124. package/dist/components/imageLightbox/index.js.map +1 -0
  125. package/dist/components/searchFilterBar/SearchFilterBar.d.ts +33 -0
  126. package/dist/components/searchFilterBar/SearchFilterBar.d.ts.map +1 -0
  127. package/dist/components/searchFilterBar/SearchFilterBar.js +76 -0
  128. package/dist/components/searchFilterBar/SearchFilterBar.js.map +1 -0
  129. package/dist/components/searchFilterBar/index.d.ts +2 -0
  130. package/dist/components/searchFilterBar/index.d.ts.map +1 -0
  131. package/dist/components/searchFilterBar/index.js +18 -0
  132. package/dist/components/searchFilterBar/index.js.map +1 -0
  133. package/dist/components/supportTicket/AttachmentChips.d.ts +10 -0
  134. package/dist/components/supportTicket/AttachmentChips.d.ts.map +1 -0
  135. package/dist/components/supportTicket/AttachmentChips.js +122 -0
  136. package/dist/components/supportTicket/AttachmentChips.js.map +1 -0
  137. package/dist/components/supportTicket/InterfaceReferenceTags.d.ts +43 -0
  138. package/dist/components/supportTicket/InterfaceReferenceTags.d.ts.map +1 -0
  139. package/dist/components/supportTicket/InterfaceReferenceTags.js +31 -0
  140. package/dist/components/supportTicket/InterfaceReferenceTags.js.map +1 -0
  141. package/dist/components/supportTicket/ReferencePicker.d.ts +87 -0
  142. package/dist/components/supportTicket/ReferencePicker.d.ts.map +1 -0
  143. package/dist/components/supportTicket/ReferencePicker.js +295 -0
  144. package/dist/components/supportTicket/ReferencePicker.js.map +1 -0
  145. package/dist/components/supportTicket/TicketHeader.d.ts +86 -0
  146. package/dist/components/supportTicket/TicketHeader.d.ts.map +1 -0
  147. package/dist/components/supportTicket/TicketHeader.js +149 -0
  148. package/dist/components/supportTicket/TicketHeader.js.map +1 -0
  149. package/dist/components/supportTicket/TicketMetaTags.d.ts +32 -0
  150. package/dist/components/supportTicket/TicketMetaTags.d.ts.map +1 -0
  151. package/dist/components/supportTicket/TicketMetaTags.js +21 -0
  152. package/dist/components/supportTicket/TicketMetaTags.js.map +1 -0
  153. package/dist/components/supportTicket/TicketReplyBox.d.ts +63 -0
  154. package/dist/components/supportTicket/TicketReplyBox.d.ts.map +1 -0
  155. package/dist/components/supportTicket/TicketReplyBox.js +115 -0
  156. package/dist/components/supportTicket/TicketReplyBox.js.map +1 -0
  157. package/dist/components/supportTicket/attachmentAccess.d.ts +37 -0
  158. package/dist/components/supportTicket/attachmentAccess.d.ts.map +1 -0
  159. package/dist/components/supportTicket/attachmentAccess.js +132 -0
  160. package/dist/components/supportTicket/attachmentAccess.js.map +1 -0
  161. package/dist/components/supportTicket/fileToAttachment.d.ts +9 -0
  162. package/dist/components/supportTicket/fileToAttachment.d.ts.map +1 -0
  163. package/dist/components/supportTicket/fileToAttachment.js +28 -0
  164. package/dist/components/supportTicket/fileToAttachment.js.map +1 -0
  165. package/dist/components/supportTicket/index.d.ts +11 -0
  166. package/dist/components/supportTicket/index.d.ts.map +1 -0
  167. package/dist/components/supportTicket/index.js +27 -0
  168. package/dist/components/supportTicket/index.js.map +1 -0
  169. package/dist/components/supportTicket/meta.d.ts +71 -0
  170. package/dist/components/supportTicket/meta.d.ts.map +1 -0
  171. package/dist/components/supportTicket/meta.js +115 -0
  172. package/dist/components/supportTicket/meta.js.map +1 -0
  173. package/dist/components/supportTicket/ticketModel.d.ts +102 -0
  174. package/dist/components/supportTicket/ticketModel.d.ts.map +1 -0
  175. package/dist/components/supportTicket/ticketModel.js +3 -0
  176. package/dist/components/supportTicket/ticketModel.js.map +1 -0
  177. package/dist/components/ticketReferences/TicketReferences.d.ts +52 -0
  178. package/dist/components/ticketReferences/TicketReferences.d.ts.map +1 -0
  179. package/dist/components/ticketReferences/TicketReferences.js +492 -0
  180. package/dist/components/ticketReferences/TicketReferences.js.map +1 -0
  181. package/dist/components/ticketReferences/index.d.ts +3 -0
  182. package/dist/components/ticketReferences/index.d.ts.map +1 -0
  183. package/dist/components/ticketReferences/index.js +6 -0
  184. package/dist/components/ticketReferences/index.js.map +1 -0
  185. package/dist/components/ticketThread/TicketThread.d.ts +57 -0
  186. package/dist/components/ticketThread/TicketThread.d.ts.map +1 -0
  187. package/dist/components/ticketThread/TicketThread.js +299 -0
  188. package/dist/components/ticketThread/TicketThread.js.map +1 -0
  189. package/dist/components/ticketThread/index.d.ts +3 -0
  190. package/dist/components/ticketThread/index.d.ts.map +1 -0
  191. package/dist/components/ticketThread/index.js +7 -0
  192. package/dist/components/ticketThread/index.js.map +1 -0
  193. package/dist/helpers/common.d.ts +5 -0
  194. package/dist/helpers/common.d.ts.map +1 -1
  195. package/dist/helpers/common.js +20 -1
  196. package/dist/helpers/common.js.map +1 -1
  197. package/dist/helpers/scrollbar.d.ts +2 -0
  198. package/dist/helpers/scrollbar.d.ts.map +1 -0
  199. package/dist/helpers/scrollbar.js +25 -0
  200. package/dist/helpers/scrollbar.js.map +1 -0
  201. package/dist/index.d.ts +8 -1
  202. package/dist/index.d.ts.map +1 -1
  203. package/dist/index.js +10 -1
  204. package/dist/index.js.map +1 -1
  205. package/dist/providers/StorageProvider.d.ts +13 -0
  206. package/dist/providers/StorageProvider.d.ts.map +1 -1
  207. package/dist/providers/StorageProvider.js +29 -5
  208. package/dist/providers/StorageProvider.js.map +1 -1
  209. package/package.json +6 -5
  210. package/src/components/FocusedEditing/index.tsx +17 -2
  211. package/src/components/composer/Composer.stories.tsx +238 -0
  212. package/src/components/composer/Composer.tsx +545 -0
  213. package/src/components/composer/helpers.ts +74 -0
  214. package/src/components/composer/index.ts +2 -0
  215. package/src/components/dpqlEditor/DpqlEditor.stories.tsx +74 -2
  216. package/src/components/form/engine/CompactRow.tsx +561 -283
  217. package/src/components/form/engine/CompactToolbar.tsx +216 -182
  218. package/src/components/form/engine/FormEngine.stories.tsx +1736 -162
  219. package/src/components/form/engine/FormEngine.tsx +886 -170
  220. package/src/components/form/engine/FormEngineRemote.stories.tsx +377 -0
  221. package/src/components/form/engine/_structuredData/StructuredDataView.stories.tsx +8 -0
  222. package/src/components/form/engine/compactRowContext.ts +14 -0
  223. package/src/components/form/engine/compactRowStyles.ts +273 -146
  224. package/src/components/form/engine/compactToolbarContext.ts +1 -0
  225. package/src/components/form/engine/optionActions.ts +40 -0
  226. package/src/components/form/engine/readFirst.ts +169 -10
  227. package/src/components/form/engine/rendererTypes.ts +55 -0
  228. package/src/components/form/engine/variants/FormEngineVariants.stories.tsx +159 -0
  229. package/src/components/form/engine/variants/VariantCalmTable.tsx +242 -0
  230. package/src/components/form/engine/variants/VariantCards.tsx +212 -0
  231. package/src/components/form/engine/variants/VariantFocus.tsx +382 -0
  232. package/src/components/form/engine/variants/VariantMinimal.tsx +170 -0
  233. package/src/components/form/engine/variants/focusDemo.ts +145 -0
  234. package/src/components/form/engine/variants/variantModel.ts +216 -0
  235. package/src/components/form/engine/variants/variantParts.tsx +313 -0
  236. package/src/components/form/expressions/ExpressionField.stories.tsx +84 -2
  237. package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +213 -19
  238. package/src/components/form/expressions/builder/index.tsx +12 -1
  239. package/src/components/form/expressions/useExpressions.ts +4 -1
  240. package/src/components/form/fields/Field.stories.tsx +40 -0
  241. package/src/components/form/fields/Field.tsx +10 -1
  242. package/src/components/form/fields/allowed-values/AllowedValues.tsx +13 -36
  243. package/src/components/form/fields/array/ArrayAutoField.stories.tsx +64 -0
  244. package/src/components/form/fields/auto/AutoFormField.stories.tsx +146 -2
  245. package/src/components/form/fields/auto/AutoFormField.tsx +42 -1
  246. package/src/components/form/fields/binary/Binary.stories.tsx +122 -0
  247. package/src/components/form/fields/binary/Binary.tsx +81 -0
  248. package/src/components/form/fields/boolean/Boolean.stories.tsx +58 -0
  249. package/src/components/form/fields/boolean/Boolean.tsx +1 -1
  250. package/src/components/form/fields/byte-size/ByteSize.stories.tsx +24 -0
  251. package/src/components/form/fields/color/Color.stories.tsx +30 -0
  252. package/src/components/form/fields/cron/Cron.stories.tsx +8 -0
  253. package/src/components/form/fields/date/Date.stories.tsx +22 -0
  254. package/src/components/form/fields/file/File.stories.tsx +48 -0
  255. package/src/components/form/fields/long-string/LongString.stories.tsx +24 -0
  256. package/src/components/form/fields/markdown/Markdown.stories.tsx +8 -0
  257. package/src/components/form/fields/multi-select/MultiSelectFormField.tsx +14 -10
  258. package/src/components/form/fields/number/Number.stories.tsx +31 -0
  259. package/src/components/form/fields/object/Object.stories.tsx +88 -0
  260. package/src/components/form/fields/radio-group/RadioGroup.stories.tsx +24 -0
  261. package/src/components/form/fields/rich-text/RichText.stories.tsx +23 -0
  262. package/src/components/form/fields/schema-definition/SchemaDefinitionField.stories.tsx +72 -0
  263. package/src/components/form/fields/select/Select.stories.tsx +108 -0
  264. package/src/components/form/fields/select/Select.tsx +38 -12
  265. package/src/components/form/fields/select/SelectCollection.tsx +78 -17
  266. package/src/components/form/fields/string/String.stories.tsx +16 -0
  267. package/src/components/form/fields/template/TemplateField.stories.tsx +160 -0
  268. package/src/components/form/fields/url/Url.stories.tsx +24 -0
  269. package/src/components/form/index.tsx +2 -0
  270. package/src/components/imageLightbox/ImageLightbox.stories.tsx +238 -0
  271. package/src/components/imageLightbox/ImageLightbox.tsx +236 -0
  272. package/src/components/imageLightbox/index.ts +1 -0
  273. package/src/components/log/Log.stories.tsx +96 -0
  274. package/src/components/menu/Menu.stories.tsx +54 -0
  275. package/src/components/qonsoleSmartInput/QonsoleSmartInput.stories.tsx +83 -37
  276. package/src/components/searchFilterBar/SearchFilterBar.tsx +131 -0
  277. package/src/components/searchFilterBar/index.ts +1 -0
  278. package/src/components/smartEditor/SmartEditor.stories.tsx +32 -0
  279. package/src/components/supportTicket/AttachmentChips.stories.tsx +171 -0
  280. package/src/components/supportTicket/AttachmentChips.tsx +147 -0
  281. package/src/components/supportTicket/InterfaceReferenceTags.stories.tsx +171 -0
  282. package/src/components/supportTicket/InterfaceReferenceTags.tsx +96 -0
  283. package/src/components/supportTicket/ReferencePicker.stories.tsx +519 -0
  284. package/src/components/supportTicket/ReferencePicker.tsx +563 -0
  285. package/src/components/supportTicket/TicketConversation.stories.tsx +121 -0
  286. package/src/components/supportTicket/TicketHeader.stories.tsx +183 -0
  287. package/src/components/supportTicket/TicketHeader.tsx +247 -0
  288. package/src/components/supportTicket/TicketMetaTags.stories.tsx +103 -0
  289. package/src/components/supportTicket/TicketMetaTags.tsx +85 -0
  290. package/src/components/supportTicket/TicketReplyBox.stories.tsx +351 -0
  291. package/src/components/supportTicket/TicketReplyBox.tsx +146 -0
  292. package/src/components/supportTicket/attachmentAccess.ts +97 -0
  293. package/src/components/supportTicket/fileToAttachment.ts +24 -0
  294. package/src/components/supportTicket/index.ts +10 -0
  295. package/src/components/supportTicket/meta.ts +185 -0
  296. package/src/components/supportTicket/ticketModel.ts +131 -0
  297. package/src/components/ticketReferences/TicketReferences.stories.tsx +315 -0
  298. package/src/components/ticketReferences/TicketReferences.tsx +886 -0
  299. package/src/components/ticketReferences/index.ts +2 -0
  300. package/src/components/ticketThread/TicketThread.stories.tsx +828 -0
  301. package/src/components/ticketThread/TicketThread.tsx +704 -0
  302. package/src/components/ticketThread/index.ts +6 -0
  303. package/src/helpers/common.ts +19 -0
  304. package/src/helpers/scrollbar.ts +32 -0
  305. package/src/hooks/useFetch/useFetch.stories.tsx +30 -0
  306. package/src/hooks/useStorage/useStorage.stories.tsx +82 -0
  307. package/src/hooks/useWebSocket/useWebsocket.stories.tsx +127 -1
  308. package/src/index.tsx +8 -1
  309. package/src/providers/StorageProvider.tsx +30 -4
  310. package/src/stores/currentUser/currentUser.stories.tsx +49 -0
@@ -0,0 +1,212 @@
1
+ /**
2
+ * VARIANT 2 — "Cards / Stack"
3
+ *
4
+ * Direction: breathing room over density. Each field is a subtle card with a
5
+ * generous tap target (phone-friendly). Status is shown with a single left
6
+ * accent — but ONLY for genuinely invalid (touched) fields; to-dos get a calm
7
+ * amber label dot, unset fields stay neutral. The description control sits on
8
+ * the LEFT under the label (where the text appears). Click a card to edit inline.
9
+ */
10
+ import { ReqoreIcon, ReqoreP } from '@qoretechnologies/reqore';
11
+ import { IQorusFormField, IQorusFormSchema } from '@qoretechnologies/ts-toolkit';
12
+ import styled from 'styled-components';
13
+ import {
14
+ InlineEdit,
15
+ STATUS_COLOR,
16
+ ValueView,
17
+ VariantToolbar,
18
+ useDisclosure,
19
+ useVariantColors,
20
+ useVariantForm,
21
+ } from './variantParts';
22
+
23
+ const Wrap = styled.div<{ $surface: string; $line: string; $hover: string }>`
24
+ display: flex;
25
+ flex-flow: column;
26
+ gap: 26px;
27
+ font-size: 13px;
28
+
29
+ .vc-card {
30
+ position: relative;
31
+ display: grid;
32
+ grid-template-columns: minmax(200px, 0.7fr) minmax(0, 1.3fr);
33
+ gap: 18px;
34
+ padding: 14px 16px;
35
+ border-radius: 10px;
36
+ background: ${({ $surface }) => $surface};
37
+ cursor: pointer;
38
+ transition: background 0.12s ease;
39
+ overflow: hidden;
40
+ }
41
+ .vc-card:hover,
42
+ .vc-card:focus-within {
43
+ background: ${({ $hover }) => $hover};
44
+ }
45
+ .vc-accent {
46
+ position: absolute;
47
+ left: 0;
48
+ top: 0;
49
+ bottom: 0;
50
+ width: 3px;
51
+ }
52
+ .vc-name {
53
+ font-weight: 600;
54
+ display: flex;
55
+ align-items: center;
56
+ gap: 7px;
57
+ }
58
+ .vc-descbtn {
59
+ background: none;
60
+ border: none;
61
+ padding: 0;
62
+ margin-top: 6px;
63
+ color: inherit;
64
+ cursor: pointer;
65
+ display: inline-flex;
66
+ align-items: center;
67
+ gap: 5px;
68
+ font-size: 12px;
69
+ }
70
+ .vc-value {
71
+ align-self: center;
72
+ min-width: 0;
73
+ }
74
+
75
+ @media (max-width: 620px) {
76
+ .vc-card {
77
+ grid-template-columns: 1fr;
78
+ gap: 8px;
79
+ padding: 14px;
80
+ }
81
+ }
82
+ `;
83
+
84
+ export const VariantCards = ({
85
+ options,
86
+ values,
87
+ }: {
88
+ options: IQorusFormSchema;
89
+ values: Record<string, IQorusFormField>;
90
+ }) => {
91
+ const c = useVariantColors();
92
+ const form = useVariantForm(options, values);
93
+ const s = form.summary;
94
+ const disc = useDisclosure();
95
+
96
+ return (
97
+ <Wrap $surface={c.surface} $line={c.line} $hover={c.hover}>
98
+ <div style={{ display: 'flex', flexFlow: 'column', gap: 10 }}>
99
+ <div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
100
+ <ReqoreP size='big' effect={{ weight: 'bold' }}>
101
+ {s.pct}% complete
102
+ </ReqoreP>
103
+ <span style={{ color: c.muted, fontSize: 12 }}>
104
+ {s.set}/{s.total} set
105
+ </span>
106
+ {s.attention ?
107
+ <button
108
+ type='button'
109
+ onClick={form.toggleAttention}
110
+ style={{
111
+ background: 'none',
112
+ border: 'none',
113
+ cursor: 'pointer',
114
+ color: c.warning,
115
+ fontSize: 12,
116
+ textDecoration: 'underline',
117
+ textUnderlineOffset: 3,
118
+ }}
119
+ >
120
+ {form.filter === 'attention' ? '← show all' : `${s.attention} need attention →`}
121
+ </button>
122
+ : null}
123
+ </div>
124
+ <div style={{ height: 4, borderRadius: 2, background: c.line, overflow: 'hidden' }}>
125
+ <div style={{ width: `${s.pct}%`, height: '100%', background: c.success }} />
126
+ </div>
127
+ </div>
128
+ <VariantToolbar form={form} />
129
+
130
+ {form.visibleGroups.map((g) => (
131
+ <div key={g.name} style={{ display: 'flex', flexFlow: 'column', gap: 10 }}>
132
+ <ReqoreP effect={{ weight: 'bold', uppercase: true, spaced: 1 }} size='small'>
133
+ {g.label}
134
+ </ReqoreP>
135
+ <div style={{ display: 'flex', flexFlow: 'column', gap: 8 }}>
136
+ {g.rows.map((r) => {
137
+ const hasDesc = !!(r.shortDesc || r.longDesc);
138
+ const open = disc.isOpen(r.name);
139
+ const editing = form.editing === r.name;
140
+ return (
141
+ <div
142
+ key={r.name}
143
+ className='vc-card'
144
+ role='button'
145
+ tabIndex={0}
146
+ onClick={() => !r.readOnly && form.startEdit(r.name)}
147
+ >
148
+ {r.status === 'invalid' ?
149
+ <span className='vc-accent' style={{ background: c.danger }} />
150
+ : null}
151
+ <div>
152
+ <span className='vc-name' style={{ color: c.text }}>
153
+ {r.status === 'todo' || r.status === 'invalid' ?
154
+ <span
155
+ aria-hidden
156
+ style={{
157
+ width: 6,
158
+ height: 6,
159
+ borderRadius: '50%',
160
+ background: STATUS_COLOR(r.status, c),
161
+ }}
162
+ />
163
+ : null}
164
+ {r.label}
165
+ {r.required ?
166
+ <ReqoreIcon icon='Asterisk' size='9px' style={{ color: c.danger }} />
167
+ : null}
168
+ </span>
169
+ {hasDesc ?
170
+ <button
171
+ type='button'
172
+ className='vc-descbtn'
173
+ style={{ color: c.faint }}
174
+ aria-expanded={open}
175
+ onClick={(e) => {
176
+ e.stopPropagation();
177
+ disc.toggle(r.name);
178
+ }}
179
+ >
180
+ <ReqoreIcon icon={open ? 'ArrowUpSLine' : 'QuestionLine'} size='12px' />
181
+ {open ? 'Hide info' : 'Info'}
182
+ </button>
183
+ : null}
184
+ {open ?
185
+ <div style={{ color: c.muted, marginTop: 6, fontSize: 12, lineHeight: 1.5 }}>
186
+ {r.shortDesc ? <div>{r.shortDesc}</div> : null}
187
+ {r.longDesc ?
188
+ <div style={{ opacity: 0.85, marginTop: r.shortDesc ? 4 : 0 }}>
189
+ {r.longDesc.replace(/[#`*]/g, '')}
190
+ </div>
191
+ : null}
192
+ </div>
193
+ : null}
194
+ </div>
195
+ <div className='vc-value' style={{ color: c.muted }}>
196
+ <ValueView value={r.value} />
197
+ {r.status === 'invalid' ?
198
+ <div style={{ color: c.danger, fontSize: 12, marginTop: 4 }}>{r.reason}</div>
199
+ : r.status === 'todo' ?
200
+ <div style={{ color: c.warning, fontSize: 12, marginTop: 4 }}>{r.reason}</div>
201
+ : null}
202
+ </div>
203
+ {editing ? <InlineEdit row={r} onDone={form.stopEdit} /> : null}
204
+ </div>
205
+ );
206
+ })}
207
+ </div>
208
+ </div>
209
+ ))}
210
+ </Wrap>
211
+ );
212
+ };
@@ -0,0 +1,382 @@
1
+ /**
2
+ * VARIANT 3 — "Focus" (production-leaning)
3
+ *
4
+ * The original Focus structure — three expandable boxes (Needs attention / Set /
5
+ * Optional) — with the engine's real functionality layered in:
6
+ * • REQUIRED (one-of) GROUPS highlighted as a "pick one" cluster in the box,
7
+ * • schema GROUPS as thin Minimal-style labels INSIDE the Set / Optional boxes,
8
+ * • a "Descriptions" toggle (short_desc under every field),
9
+ * • COMPLEX fields showing their nested value inline with Show-more,
10
+ * • real inline editing (AutoFormField) on click.
11
+ *
12
+ * Attention box and the rest are EXCLUSIVE: a field needing attention sits in the
13
+ * box until resolved, then drops into Set (or Optional) below.
14
+ */
15
+ import { ReqoreIcon, ReqoreP } from '@qoretechnologies/reqore';
16
+ import { IQorusFormField, IQorusFormSchema } from '@qoretechnologies/ts-toolkit';
17
+ import React from 'react';
18
+ import styled from 'styled-components';
19
+ import { IVariantGroup, IVariantRow, TVariantStatus } from './variantModel';
20
+ import {
21
+ ComplexPreview,
22
+ InlineEdit,
23
+ STATUS_COLOR,
24
+ StatusDot,
25
+ TVariantForm,
26
+ ValueView,
27
+ VariantToolbar,
28
+ useVariantColors,
29
+ useVariantForm,
30
+ } from './variantParts';
31
+
32
+ const Wrap = styled.div<{ $hover: string; $faint: string }>`
33
+ display: flex;
34
+ flex-flow: column;
35
+ gap: 18px;
36
+ font-size: 13px;
37
+
38
+ .vf-row {
39
+ display: grid;
40
+ grid-template-columns: minmax(190px, 320px) minmax(0, 1fr) auto;
41
+ column-gap: 16px;
42
+ row-gap: 2px;
43
+ align-items: center;
44
+ /* Single-line rows: centre the content within the min-height. */
45
+ align-content: center;
46
+ min-height: 38px;
47
+ padding: 6px 12px;
48
+ border-radius: 8px;
49
+ cursor: pointer;
50
+ }
51
+ /* Rows with a description / preview / editor below: pin the name+value to the
52
+ top instead of centring the whole block. */
53
+ .vf-row.vf-tall {
54
+ align-content: start;
55
+ }
56
+ .vf-row:hover,
57
+ .vf-row[aria-expanded='true'] {
58
+ background: ${({ $hover }) => $hover};
59
+ }
60
+ .vf-section {
61
+ border-radius: 12px;
62
+ padding: 6px;
63
+ }
64
+ .vf-sechead {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 8px;
68
+ padding: 8px 12px;
69
+ cursor: pointer;
70
+ user-select: none;
71
+ }
72
+ .vf-grouplabel {
73
+ font-size: 10px;
74
+ letter-spacing: 1px;
75
+ text-transform: uppercase;
76
+ padding: 10px 0 2px 12px;
77
+ }
78
+ .vf-cluster {
79
+ border-radius: 10px;
80
+ padding: 4px 4px 6px;
81
+ margin: 2px 0;
82
+ }
83
+ .vf-desc {
84
+ grid-column: 1 / -1;
85
+ padding: 0 0 2px;
86
+ font-size: 12px;
87
+ line-height: 1.45;
88
+ }
89
+ .vf-preview {
90
+ grid-column: 2 / -1;
91
+ padding: 2px 0 4px;
92
+ }
93
+ @media (max-width: 640px) {
94
+ .vf-row {
95
+ grid-template-columns: 1fr auto;
96
+ grid-template-areas: 'label dot' 'value value';
97
+ row-gap: 2px;
98
+ }
99
+ .vf-label {
100
+ grid-area: label;
101
+ }
102
+ .vf-value {
103
+ grid-area: value;
104
+ }
105
+ .vf-dot {
106
+ grid-area: dot;
107
+ }
108
+ .vf-preview {
109
+ grid-column: 1 / -1;
110
+ }
111
+ }
112
+ `;
113
+
114
+ const Row = ({ r, form, showDesc }: { r: IVariantRow; form: TVariantForm; showDesc: boolean }) => {
115
+ const c = useVariantColors();
116
+ const editing = form.editing === r.name;
117
+ const isHash = r.value.kind === 'hash';
118
+ // A row is "tall" (has something below the name/value line) when editing, when
119
+ // it's a complex preview, or when descriptions are shown for it.
120
+ const tall = editing || isHash || (showDesc && !!r.shortDesc);
121
+ return (
122
+ <div
123
+ className={tall ? 'vf-row vf-tall' : 'vf-row'}
124
+ role='button'
125
+ tabIndex={0}
126
+ aria-expanded={editing}
127
+ onClick={() => !r.readOnly && form.startEdit(r.name)}
128
+ >
129
+ <span
130
+ className='vf-label'
131
+ style={{ fontWeight: 600, color: c.text, display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }}
132
+ >
133
+ {r.label}
134
+ {r.required ?
135
+ <ReqoreIcon icon='Asterisk' size='9px' style={{ color: c.danger }} />
136
+ : null}
137
+ </span>
138
+ <span className='vf-value' style={{ color: c.muted, minWidth: 0 }}>
139
+ <ValueView value={r.value} />
140
+ {r.reason && r.status !== 'set' ?
141
+ <span style={{ color: STATUS_COLOR(r.status, c), fontSize: 12, marginLeft: 8 }}>
142
+ {r.reason}
143
+ </span>
144
+ : null}
145
+ </span>
146
+ <span className='vf-dot' style={{ display: 'inline-flex', justifyContent: 'flex-end' }}>
147
+ <StatusDot status={r.status} />
148
+ </span>
149
+ {showDesc && r.shortDesc && !editing ?
150
+ <div className='vf-desc' style={{ color: c.faint }}>
151
+ {r.shortDesc}
152
+ </div>
153
+ : null}
154
+ {isHash && !editing ?
155
+ <div className='vf-preview'>
156
+ <ComplexPreview value={r.field?.value} onOpen={() => !r.readOnly && form.startEdit(r.name)} />
157
+ </div>
158
+ : null}
159
+ {editing ?
160
+ <>
161
+ {r.shortDesc ? <div className='vf-desc' style={{ color: c.faint }}>{r.shortDesc}</div> : null}
162
+ <InlineEdit row={r} onDone={form.stopEdit} />
163
+ </>
164
+ : null}
165
+ </div>
166
+ );
167
+ };
168
+
169
+ /** A one-of required group, highlighted: "pick one of these". */
170
+ const RequiredCluster = ({
171
+ rows,
172
+ form,
173
+ showDesc,
174
+ }: {
175
+ rows: IVariantRow[];
176
+ form: TVariantForm;
177
+ showDesc: boolean;
178
+ }) => {
179
+ const c = useVariantColors();
180
+ const tint = c.warning;
181
+ return (
182
+ <div className='vf-cluster' style={{ background: `${tint}10`, border: `1px solid ${tint}33` }}>
183
+ {/* Matches the thin group-label style (.vf-grouplabel) for compactness. */}
184
+ <div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 0 2px 12px' }}>
185
+ <ReqoreIcon icon='LinkM' size='11px' style={{ color: tint }} />
186
+ <span style={{ fontSize: 10, letterSpacing: 1, textTransform: 'uppercase', color: tint }}>
187
+ One of the below is required
188
+ </span>
189
+ </div>
190
+ {rows.map((r) => <Row key={r.name} r={r} form={form} showDesc={showDesc} />)}
191
+ </div>
192
+ );
193
+ };
194
+
195
+ const Section = ({
196
+ title,
197
+ intent,
198
+ count,
199
+ children,
200
+ defaultOpen = true,
201
+ }: {
202
+ title: string;
203
+ intent: string;
204
+ count: number;
205
+ children: React.ReactNode;
206
+ defaultOpen?: boolean;
207
+ }) => {
208
+ const [open, setOpen] = React.useState(defaultOpen);
209
+ if (!count) return null;
210
+ return (
211
+ <div className='vf-section' style={{ background: `${intent}0e`, border: `1px solid ${intent}22` }}>
212
+ <div className='vf-sechead' onClick={() => setOpen((o) => !o)}>
213
+ <ReqoreIcon icon={open ? 'ArrowDownSLine' : 'ArrowRightSLine'} size='15px' />
214
+ <ReqoreP effect={{ weight: 'bold' }}>{title}</ReqoreP>
215
+ <span
216
+ style={{
217
+ background: `${intent}22`,
218
+ color: intent,
219
+ borderRadius: 20,
220
+ padding: '1px 9px',
221
+ fontSize: 12,
222
+ fontWeight: 700,
223
+ }}
224
+ >
225
+ {count}
226
+ </span>
227
+ </div>
228
+ {open ? <div>{children}</div> : null}
229
+ </div>
230
+ );
231
+ };
232
+
233
+ /** Rows of a given status, grouped by schema group with thin Minimal-style
234
+ * labels. Used inside the Set and Optional boxes. */
235
+ const GroupedRows = ({
236
+ groups,
237
+ status,
238
+ skip,
239
+ form,
240
+ showDesc,
241
+ }: {
242
+ groups: IVariantGroup[];
243
+ status: TVariantStatus;
244
+ skip: Set<string>;
245
+ form: TVariantForm;
246
+ showDesc: boolean;
247
+ }) => {
248
+ const c = useVariantColors();
249
+ const sections = groups
250
+ .map((g) => ({ g, rows: g.rows.filter((r) => r.status === status && !skip.has(r.name)) }))
251
+ .filter((s) => s.rows.length);
252
+ return (
253
+ <>
254
+ {sections.map(({ g, rows }) => (
255
+ <div key={g.name}>
256
+ {/* only label when there's more than one group's worth, else it's noise */}
257
+ {sections.length > 1 ?
258
+ <div className='vf-grouplabel' style={{ color: c.faint }}>
259
+ {g.label}
260
+ </div>
261
+ : null}
262
+ {rows.map((r) => <Row key={r.name} r={r} form={form} showDesc={showDesc} />)}
263
+ </div>
264
+ ))}
265
+ </>
266
+ );
267
+ };
268
+
269
+ export const VariantFocus = ({
270
+ options,
271
+ values,
272
+ config,
273
+ }: {
274
+ options: IQorusFormSchema;
275
+ values: Record<string, IQorusFormField>;
276
+ config?: any;
277
+ }) => {
278
+ const c = useVariantColors();
279
+ const form = useVariantForm(options, values, config);
280
+ const s = form.summary;
281
+ const showDesc = form.showDescriptions;
282
+
283
+ const allRows = form.visibleGroups.flatMap((g) => g.rows);
284
+ const unmetGroups = Object.values(form.requiredGroups).filter((g) => !g.satisfied);
285
+ const unmetMembers = new Set<string>();
286
+ unmetGroups.forEach((g) => g.members.forEach((m) => unmetMembers.add(m)));
287
+
288
+ // Attention = invalid + individually-required-todo (NOT one-of members, which
289
+ // show as their cluster) + the unmet one-of clusters.
290
+ const attentionRows = allRows.filter(
291
+ (r) => (r.status === 'invalid' || r.status === 'todo') && !unmetMembers.has(r.name)
292
+ );
293
+ const attentionCount = attentionRows.length + unmetGroups.length;
294
+ const skip = new Set<string>(attentionRows.map((r) => r.name));
295
+ unmetMembers.forEach((n) => skip.add(n));
296
+
297
+ const setCount = allRows.filter((r) => r.status === 'set' && !skip.has(r.name)).length;
298
+ const optionalCount = allRows.filter((r) => r.status === 'unset' && !skip.has(r.name)).length;
299
+
300
+ // Group the attention items by schema group (placing each one-of cluster under
301
+ // its members' group) so the attention box reads like the Set / Optional boxes.
302
+ const clusterGroupName = (members: string[]) =>
303
+ form.visibleGroups.find((g) => g.rows.some((r) => r.name === members[0]))?.name;
304
+ const attnGroups = form.visibleGroups
305
+ .map((g) => ({
306
+ g,
307
+ rows: g.rows.filter(
308
+ (r) => (r.status === 'invalid' || r.status === 'todo') && !unmetMembers.has(r.name)
309
+ ),
310
+ clusters: unmetGroups.filter((rg) => clusterGroupName(rg.members) === g.name),
311
+ }))
312
+ .filter((x) => x.rows.length || x.clusters.length);
313
+
314
+ return (
315
+ <Wrap $hover={c.hover} $faint={c.faint}>
316
+ {/* Cards-style header */}
317
+ <div style={{ display: 'flex', flexFlow: 'column', gap: 10 }}>
318
+ <div style={{ display: 'flex', alignItems: 'baseline', gap: 12, flexWrap: 'wrap' }}>
319
+ <ReqoreP size='big' effect={{ weight: 'bold' }}>
320
+ {s.pct}% complete
321
+ </ReqoreP>
322
+ <span style={{ color: c.muted, fontSize: 12 }}>
323
+ {s.set}/{s.total} set
324
+ </span>
325
+ {s.attention ?
326
+ <button
327
+ type='button'
328
+ onClick={form.toggleAttention}
329
+ style={{
330
+ background: 'none',
331
+ border: 'none',
332
+ cursor: 'pointer',
333
+ color: c.warning,
334
+ fontSize: 12,
335
+ textDecoration: 'underline',
336
+ textUnderlineOffset: 3,
337
+ }}
338
+ >
339
+ {form.filter === 'attention' ? '← show all' : `${s.attention} need attention →`}
340
+ </button>
341
+ : null}
342
+ </div>
343
+ <div style={{ height: 4, borderRadius: 2, background: c.line, overflow: 'hidden', display: 'flex' }}>
344
+ <div style={{ width: `${(s.set / s.total) * 100}%`, background: c.success }} />
345
+ <div style={{ width: `${(s.attention / s.total) * 100}%`, background: c.warning }} />
346
+ </div>
347
+ </div>
348
+ <VariantToolbar form={form} />
349
+
350
+ {/* The signature: Needs attention / Set / Optional expandable boxes —
351
+ attention items grouped by schema group, like the other boxes. */}
352
+ <Section title='Needs attention' intent={c.warning} count={attentionCount}>
353
+ {attnGroups.map(({ g, rows, clusters }) => (
354
+ <div key={g.name}>
355
+ {attnGroups.length > 1 ?
356
+ <div className='vf-grouplabel' style={{ color: c.faint }}>
357
+ {g.label}
358
+ </div>
359
+ : null}
360
+ {clusters.map((cl) => (
361
+ <RequiredCluster
362
+ key={cl.key}
363
+ rows={allRows.filter((r) => cl.members.includes(r.name))}
364
+ form={form}
365
+ showDesc={showDesc}
366
+ />
367
+ ))}
368
+ {rows.map((r) => <Row key={r.name} r={r} form={form} showDesc={showDesc} />)}
369
+ </div>
370
+ ))}
371
+ </Section>
372
+
373
+ <Section title='Set' intent={c.success} count={setCount}>
374
+ <GroupedRows groups={form.visibleGroups} status='set' skip={skip} form={form} showDesc={showDesc} />
375
+ </Section>
376
+
377
+ <Section title='Optional — not set' intent={c.muted} count={optionalCount} defaultOpen={false}>
378
+ <GroupedRows groups={form.visibleGroups} status='unset' skip={skip} form={form} showDesc={showDesc} />
379
+ </Section>
380
+ </Wrap>
381
+ );
382
+ };