@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,6 @@
1
+ export { TicketThread, ticketMessageDomId } from './TicketThread';
2
+ export type {
3
+ ITicketThreadAttachment,
4
+ ITicketThreadMessage,
5
+ ITicketThreadProps,
6
+ } from './TicketThread';
@@ -174,6 +174,25 @@ export const splitByteSize = (value?: string): [number | undefined, string | und
174
174
  return [amount?.[0] ? Number(amount[0]) : undefined, unit?.[0]];
175
175
  };
176
176
 
177
+ /** Human-readable file size, e.g. `"12.3 KB"` — whole bytes below 1 KiB, one
178
+ * decimal above. Shared by the ticket thread / references attachment rows. */
179
+ export const formatBytes = (bytes: number): string => {
180
+ if (bytes < 1024) {
181
+ return `${bytes} B`;
182
+ }
183
+ const units = ['KB', 'MB', 'GB'];
184
+ let value = bytes / 1024;
185
+ let unit = 0;
186
+ while (value >= 1024 && unit < units.length - 1) {
187
+ value /= 1024;
188
+ unit += 1;
189
+ }
190
+ return `${value.toFixed(1)} ${units[unit]}`;
191
+ };
192
+
193
+ /** True when a MIME content type is an image (`image/*`). */
194
+ export const isImage = (contentType: string): boolean => contentType.startsWith('image/');
195
+
177
196
  // URL value splitting (IDE `urlField.tsx` behavior). Lives here rather than
178
197
  // in the Url component so `validations.ts` can use it without pulling the
179
198
  // component chain into non-DOM consumers (jest).
@@ -0,0 +1,32 @@
1
+ import { css } from 'styled-components';
2
+
3
+ /*
4
+ * The thin, unobtrusive scrollbar the Qorus surfaces use — a transparent track and a
5
+ * translucent rounded thumb — in place of the platform default, which reads as heavy
6
+ * chrome inside a dark panel. Drop it into any `styled` scroll container:
7
+ *
8
+ * const Scroll = styled.div`
9
+ * overflow-y: auto;
10
+ * ${thinScrollbar}
11
+ * `;
12
+ *
13
+ * webkit-only on purpose: setting `scrollbar-width` as well lets Chromium override this
14
+ * custom 6px with its own wider "thin", so the two disagree. Firefox keeps its native
15
+ * bar, which is already slim.
16
+ */
17
+ export const thinScrollbar = css`
18
+ &::-webkit-scrollbar {
19
+ width: 6px;
20
+ height: 6px;
21
+ }
22
+ &::-webkit-scrollbar-track {
23
+ background: transparent;
24
+ }
25
+ &::-webkit-scrollbar-thumb {
26
+ background: rgba(255, 255, 255, 0.15);
27
+ border-radius: 3px;
28
+ }
29
+ &::-webkit-scrollbar-thumb:hover {
30
+ background: rgba(255, 255, 255, 0.28);
31
+ }
32
+ `;
@@ -35,6 +35,12 @@ export const Get: Story = {
35
35
  method: 'GET',
36
36
  },
37
37
  parameters: {
38
+ docs: {
39
+ description: {
40
+ story:
41
+ 'Renders a useFetch demo that issues a GET against /public/info on mount and displays the returned Qorus system info as a tree.',
42
+ },
43
+ },
38
44
  mockData: [
39
45
  {
40
46
  // An array of mock objects which will add in every story
@@ -66,6 +72,12 @@ export const Put: Story = {
66
72
  method: 'PUT',
67
73
  },
68
74
  parameters: {
75
+ docs: {
76
+ description: {
77
+ story:
78
+ 'Renders a useFetch demo that issues a PUT against /public/info on mount and displays the returned success status.',
79
+ },
80
+ },
69
81
  mockData: [
70
82
  {
71
83
  // An array of mock objects which will add in every story
@@ -88,6 +100,12 @@ export const Post: Story = {
88
100
  method: 'POST',
89
101
  },
90
102
  parameters: {
103
+ docs: {
104
+ description: {
105
+ story:
106
+ 'Renders a useFetch demo that issues a POST against /public/info on mount and displays the returned success status.',
107
+ },
108
+ },
91
109
  mockData: [
92
110
  {
93
111
  // An array of mock objects which will add in every story
@@ -110,6 +128,12 @@ export const Del: Story = {
110
128
  method: 'DELETE',
111
129
  },
112
130
  parameters: {
131
+ docs: {
132
+ description: {
133
+ story:
134
+ 'Renders a useFetch demo that issues a DELETE against /public/info on mount and displays the returned success status.',
135
+ },
136
+ },
113
137
  mockData: [
114
138
  {
115
139
  // An array of mock objects which will add in every story
@@ -132,6 +156,12 @@ export const Error409: Story = {
132
156
  method: 'POST',
133
157
  },
134
158
  parameters: {
159
+ docs: {
160
+ description: {
161
+ story:
162
+ 'Renders a useFetch demo that issues a POST returning a 409 error — the hook exposes errorData and the Qorus stack trace is rendered in place of the response.',
163
+ },
164
+ },
135
165
  mockData: [
136
166
  {
137
167
  // An array of mock objects which will add in every story
@@ -1,5 +1,6 @@
1
1
  import { ReqoreButton, ReqoreControlGroup, ReqoreP } from '@qoretechnologies/reqore';
2
2
  import { StoryObj } from '@storybook/react-vite';
3
+ import { useRef } from 'react';
3
4
  import { fireEvent, within } from 'storybook/test';
4
5
  import { storiesStorageMock } from '../../../__tests__/ mock';
5
6
  import { testsWaitForText } from '../../../__tests__/utils';
@@ -35,6 +36,12 @@ export const DefaultValue: Story = {
35
36
  method: 'GET',
36
37
  },
37
38
  parameters: {
39
+ docs: {
40
+ description: {
41
+ story:
42
+ 'Renders a useReqraftStorage demo where the server returns no stored value — the hook falls back to the default and displays it.',
43
+ },
44
+ },
38
45
  mockData: [
39
46
  {
40
47
  url: 'https://hq.qoretechnologies.com:8092/api/latest/users?action=current',
@@ -51,6 +58,12 @@ export const DefaultValue: Story = {
51
58
 
52
59
  export const StorageValue: Story = {
53
60
  parameters: {
61
+ docs: {
62
+ description: {
63
+ story:
64
+ 'Renders a useReqraftStorage demo with a value already persisted on the server — the hook loads it and displays it in place of the default.',
65
+ },
66
+ },
54
67
  mockData: [...storiesStorageMock],
55
68
  },
56
69
  play: async () => {
@@ -60,6 +73,15 @@ export const StorageValue: Story = {
60
73
 
61
74
  export const ValueCanBeUpdated: Story = {
62
75
  ...StorageValue,
76
+ parameters: {
77
+ ...StorageValue.parameters,
78
+ docs: {
79
+ description: {
80
+ story:
81
+ 'Renders the useReqraftStorage demo with a stored value. When "Update storage" is clicked, the setter writes a new value and the display refreshes to match.',
82
+ },
83
+ },
84
+ },
63
85
  play: async ({ canvasElement }) => {
64
86
  const canvas = within(canvasElement);
65
87
 
@@ -72,8 +94,68 @@ export const ValueCanBeUpdated: Story = {
72
94
  },
73
95
  };
74
96
 
97
+ /**
98
+ * Regression for the "stale updater clobbers other keys" bug: the storage
99
+ * updater must persist onto the LATEST storage blob, not the one captured in
100
+ * its closure. Long-lived callers (an imperative store subscription, a ref to
101
+ * the mount-time setter) hold an old updater; writing through it must not wipe
102
+ * keys other hooks wrote in the meantime.
103
+ */
104
+ export const StaleUpdaterDoesNotClobberOtherKeys: Story = {
105
+ args: { reqraftOptions: { waitForStorage: true } },
106
+ parameters: {
107
+ docs: {
108
+ description: {
109
+ story:
110
+ "Renders two useReqraftStorage keys and captures the mount-time setter for the first one (a deliberately STALE updater). After a second key is written, writing through the stale updater must keep the second key intact — proving storage writes merge onto the latest blob rather than a closure copy. This is the regression test for page-visit tracking wiping other stored settings.",
111
+ },
112
+ },
113
+ mockData: [...storiesStorageMock],
114
+ },
115
+ render: () => {
116
+ const [someVal, setSomeVal] = useReqraftStorage<string>('some-path', 'default');
117
+ const [keyB] = useReqraftStorage<string>('key-b', 'b-default');
118
+ const [, setKeyB] = useReqraftStorage<string>('key-b', 'b-default');
119
+ // Capture the mount-time setter — it stays stale once other keys change.
120
+ const staleSetSomeVal = useRef(setSomeVal);
121
+
122
+ return (
123
+ <ReqoreControlGroup vertical>
124
+ <ReqoreP>{`some-path: ${someVal}`}</ReqoreP>
125
+ <ReqoreP>{`key-b: ${keyB}`}</ReqoreP>
126
+ <ReqoreButton onClick={() => setKeyB('b-written')}>Write B</ReqoreButton>
127
+ <ReqoreButton onClick={() => staleSetSomeVal.current('a-written')}>
128
+ Write A (stale)
129
+ </ReqoreButton>
130
+ </ReqoreControlGroup>
131
+ );
132
+ },
133
+ play: async ({ canvasElement }) => {
134
+ const canvas = within(canvasElement);
135
+
136
+ await testsWaitForText('some-path: This is a storage value');
137
+ // Write a second key through its own (fresh) updater.
138
+ await fireEvent.click(canvas.getByText('Write B'));
139
+ await testsWaitForText('key-b: b-written');
140
+ // Write the first key through the STALE mount-time updater.
141
+ await fireEvent.click(canvas.getByText('Write A (stale)'));
142
+ await testsWaitForText('some-path: a-written');
143
+ // The stale write must NOT have wiped the second key.
144
+ await testsWaitForText('key-b: b-written');
145
+ },
146
+ };
147
+
75
148
  export const ValueCanBeRemoved: Story = {
76
149
  ...StorageValue,
150
+ parameters: {
151
+ ...StorageValue.parameters,
152
+ docs: {
153
+ description: {
154
+ story:
155
+ 'Renders the useReqraftStorage demo with a stored value. When "Remove value" is clicked, the remover clears storage and the hook falls back to the default value.',
156
+ },
157
+ },
158
+ },
77
159
  play: async ({ canvasElement }) => {
78
160
  const canvas = within(canvasElement);
79
161
 
@@ -106,8 +106,25 @@ const meta = {
106
106
  export default meta;
107
107
  export type Story = StoryObj<typeof meta>;
108
108
 
109
- export const Default: Story = {};
109
+ export const Default: Story = {
110
+ parameters: {
111
+ docs: {
112
+ description: {
113
+ story:
114
+ 'Renders the useReqraftWebSocket demo with no lifecycle options — the socket stays CLOSED until the operator clicks Connect.',
115
+ },
116
+ },
117
+ },
118
+ };
110
119
  export const OpenManually: Story = {
120
+ parameters: {
121
+ docs: {
122
+ description: {
123
+ story:
124
+ 'Renders the useReqraftWebSocket demo and clicks the Connect action — the socket opens and the onOpen callback fires.',
125
+ },
126
+ },
127
+ },
111
128
  play: async ({ args }) => {
112
129
  await testsClickButton({ label: 'Connect' });
113
130
  await testsWaitForText('Websocket Status: OPEN');
@@ -118,6 +135,14 @@ export const OpenOnMount: Story = {
118
135
  args: {
119
136
  openOnMount: true,
120
137
  },
138
+ parameters: {
139
+ docs: {
140
+ description: {
141
+ story:
142
+ 'Renders the useReqraftWebSocket demo with openOnMount enabled — the socket opens immediately after mount without any user action.',
143
+ },
144
+ },
145
+ },
121
146
  play: async ({ args }) => {
122
147
  await testsWaitForText('Websocket Status: OPEN');
123
148
  await expect(args.onOpen).toHaveBeenCalled();
@@ -126,6 +151,15 @@ export const OpenOnMount: Story = {
126
151
 
127
152
  export const CloseManually: Story = {
128
153
  ...OpenOnMount,
154
+ parameters: {
155
+ ...OpenOnMount.parameters,
156
+ docs: {
157
+ description: {
158
+ story:
159
+ 'Renders the useReqraftWebSocket demo opened on mount. When Disconnect is clicked, the socket transitions to CLOSED and the onClose callback fires.',
160
+ },
161
+ },
162
+ },
129
163
  play: async ({ args, ...rest }) => {
130
164
  await OpenOnMount.play({ args, ...rest });
131
165
  await testsClickButton({ label: 'Disconnect' });
@@ -141,6 +175,14 @@ export const Reconnects: Story = {
141
175
  maxReconnectTries: 5,
142
176
  openOnMount: true,
143
177
  },
178
+ parameters: {
179
+ docs: {
180
+ description: {
181
+ story:
182
+ 'Renders the useReqraftWebSocket demo with reconnect enabled. When the server is killed, the socket transitions to CONNECTING and then back to OPEN once the server returns.',
183
+ },
184
+ },
185
+ },
144
186
  play: async ({ args, ...rest }) => {
145
187
  await OpenOnMount.play({ args, ...rest });
146
188
  await testsClickButton({ label: 'Kill' });
@@ -159,6 +201,12 @@ export const ReconnectFails: Story = {
159
201
  reconnectInterval: 500,
160
202
  },
161
203
  parameters: {
204
+ docs: {
205
+ description: {
206
+ story:
207
+ 'Renders the useReqraftWebSocket demo with a low maxReconnectTries. When the server never returns, the socket exhausts its reconnect attempts and fires onReconnectFailed.',
208
+ },
209
+ },
162
210
  jest: {
163
211
  timeout: 60000,
164
212
  },
@@ -178,6 +226,14 @@ export const SendMessage: Story = {
178
226
  includeSentMessagesInState: true,
179
227
  useState: true,
180
228
  },
229
+ parameters: {
230
+ docs: {
231
+ description: {
232
+ story:
233
+ 'Renders the useReqraftWebSocket demo with useState enabled. When Send is clicked, the message is dispatched and the echoed reply appears in the accumulated message list.',
234
+ },
235
+ },
236
+ },
181
237
  play: async ({ args, ...rest }) => {
182
238
  await OpenOnMount.play({ args, ...rest });
183
239
  await testsClickButton({ label: 'Send' });
@@ -197,6 +253,14 @@ export const WithLogs: Story = {
197
253
  useState: true,
198
254
  reconnectInterval: 1500,
199
255
  },
256
+ parameters: {
257
+ docs: {
258
+ description: {
259
+ story:
260
+ 'Renders the useReqraftWebSocket demo with includeLogMessagesInState. Connect / disconnect / reconnect lifecycle events are captured as log entries alongside regular messages.',
261
+ },
262
+ },
263
+ },
200
264
  play: async ({ args, ...rest }) => {
201
265
  await Reconnects.play({ args, ...rest });
202
266
 
@@ -206,6 +270,15 @@ export const WithLogs: Story = {
206
270
 
207
271
  export const ClearsMessages: Story = {
208
272
  ...SendMessage,
273
+ parameters: {
274
+ ...SendMessage.parameters,
275
+ docs: {
276
+ description: {
277
+ story:
278
+ 'Renders the useReqraftWebSocket demo after sending a message. When Clear is clicked, the accumulated message list is emptied.',
279
+ },
280
+ },
281
+ },
209
282
  play: async ({ args, ...rest }) => {
210
283
  await SendMessage.play({ args, ...rest });
211
284
  await testsClickButton({ label: 'Clear' });
@@ -353,6 +426,14 @@ export const MultipleConnections: Story = {
353
426
  includeLogMessagesInState: true,
354
427
  useState: true,
355
428
  },
429
+ parameters: {
430
+ docs: {
431
+ description: {
432
+ story:
433
+ 'Renders three panels that each call useReqraftWebSocket against the same URL — proves the connections share one pooled underlying WebSocket while keeping independent message state.',
434
+ },
435
+ },
436
+ },
356
437
  // @ts-expect-error customprops
357
438
  render: (args: IUseReqraftWebSocketOptions) => {
358
439
  const [conectionStatus, setConnectionStatus] = useState<string>('CLOSED');
@@ -405,6 +486,15 @@ export const MultipleConnectionsOpenOnMount: Story = {
405
486
  ...MultipleConnections.args,
406
487
  openOnMount: true,
407
488
  },
489
+ parameters: {
490
+ ...MultipleConnections.parameters,
491
+ docs: {
492
+ description: {
493
+ story:
494
+ 'Renders three pooled useReqraftWebSocket panels with openOnMount — all three transition to OPEN over the shared underlying socket without any user action.',
495
+ },
496
+ },
497
+ },
408
498
  play: async ({ args }) => {
409
499
  await testsWaitForText('First Connection Status: OPEN');
410
500
  await testsWaitForText('Second Connection Status: OPEN');
@@ -420,6 +510,15 @@ export const MultipleConnectionsClosedAtOnce: Story = {
420
510
  ...MultipleConnections.args,
421
511
  openOnMount: true,
422
512
  },
513
+ parameters: {
514
+ ...MultipleConnections.parameters,
515
+ docs: {
516
+ description: {
517
+ story:
518
+ 'Renders three pooled useReqraftWebSocket panels. When Close All closes the shared socket, every consumer transitions to CLOSED at once and each panel logs its close handler.',
519
+ },
520
+ },
521
+ },
423
522
  play: async (args) => {
424
523
  await MultipleConnectionsOpenOnMount.play(args);
425
524
 
@@ -436,6 +535,15 @@ export const ConnectionIsClosedWhenAllUsersAreClosed: Story = {
436
535
  ...MultipleConnections.args,
437
536
  openOnMount: true,
438
537
  },
538
+ parameters: {
539
+ ...MultipleConnections.parameters,
540
+ docs: {
541
+ description: {
542
+ story:
543
+ 'Renders three pooled useReqraftWebSocket panels. Closing them one by one keeps the shared socket OPEN until the last consumer unmounts, then it closes.',
544
+ },
545
+ },
546
+ },
439
547
  play: async (args) => {
440
548
  await MultipleConnectionsOpenOnMount.play(args);
441
549
 
@@ -454,6 +562,15 @@ export const MultipleConnectionsHaveCustomHandlers: Story = {
454
562
  ...MultipleConnections.args,
455
563
  openOnMount: true,
456
564
  },
565
+ parameters: {
566
+ ...MultipleConnections.parameters,
567
+ docs: {
568
+ description: {
569
+ story:
570
+ 'Renders three pooled useReqraftWebSocket panels. Each panel attaches its own message handler, and sending a message triggers the handlers of only the connections still open.',
571
+ },
572
+ },
573
+ },
457
574
  play: async (args) => {
458
575
  const canvas = within(args.canvasElement);
459
576
 
@@ -480,6 +597,15 @@ export const MultipleConnectionsHaveCustomHandlers: Story = {
480
597
 
481
598
  export const MultipleConnectionsCanBeDisconnectedAndReconnected: Story = {
482
599
  ...MultipleConnectionsHaveCustomHandlers,
600
+ parameters: {
601
+ ...MultipleConnectionsHaveCustomHandlers.parameters,
602
+ docs: {
603
+ description: {
604
+ story:
605
+ 'Renders three pooled useReqraftWebSocket panels after custom handlers have fired. Disconnecting one and reconnecting another proves individual consumers can toggle without disturbing peers.',
606
+ },
607
+ },
608
+ },
483
609
  play: async (args) => {
484
610
  await MultipleConnectionsHaveCustomHandlers.play(args);
485
611
 
package/src/index.tsx CHANGED
@@ -10,6 +10,8 @@ export {
10
10
  validateOptionWithRequiredGroups,
11
11
  } from './helpers/validations';
12
12
  export type { IValidationResult } from './helpers/validations';
13
+ export { thinScrollbar } from './helpers/scrollbar';
14
+ export * from './components/imageLightbox';
13
15
  export { ReqraftMenu } from './components/menu/Menu';
14
16
  export type {
15
17
  IReqraftMenuItem,
@@ -33,7 +35,7 @@ export type {
33
35
  ICurrentUser,
34
36
  ICurrentUserStore,
35
37
  } from './stores/currentUser/currentUser';
36
- export { query } from './utils/fetch';
38
+ export { fetchConfig, query } from './utils/fetch';
37
39
  export * from './utils/websocket';
38
40
  export { ReqraftLspClient } from './utils/lspClient';
39
41
  export type { IReqraftLspClientOptions } from './utils/lspClient';
@@ -52,6 +54,11 @@ export type {
52
54
  ILspTextEdit,
53
55
  TLspDocumentText,
54
56
  } from './utils/lspClient.types';
57
+ export * from './components/composer';
55
58
  export * from './components/smartEditor';
56
59
  export * from './components/dpqlEditor';
57
60
  export * from './components/qonsoleSmartInput';
61
+ export * from './components/supportTicket';
62
+ export * from './components/ticketThread';
63
+ export * from './components/ticketReferences';
64
+ export * from './components/searchFilterBar';
@@ -9,6 +9,23 @@ import { TReqraftStorageValue } from '../hooks/useStorage/useStorage';
9
9
  import { currentUserStore } from '../stores/currentUser/currentUser';
10
10
  import { IReqraftProviderProps } from './ReqraftProvider';
11
11
 
12
+ /**
13
+ * Apply a single path write onto the current storage blob, seeding a fresh object
14
+ * when the user has no storage yet.
15
+ *
16
+ * `?? {}` is load-bearing: a user who has never written storage has
17
+ * `storage === undefined | null`, and `set(cloneDeep(undefined), …)` returns
18
+ * `undefined` (lodash won't set on a nullish target). Without the seed the FIRST
19
+ * write for such a user — e.g. accepting the cookie-consent banner — persists an
20
+ * empty body and silently drops the value, so the flag stays unset forever and
21
+ * the banner never closes. Exported for testing.
22
+ */
23
+ export const applyStorageWrite = (
24
+ storage: TReqraftStorage | undefined,
25
+ path: string,
26
+ value: unknown
27
+ ): TReqraftStorage => set(cloneDeep(storage) ?? {}, path, value);
28
+
12
29
  export interface IReqraftStorageProviderProps
13
30
  extends Pick<IReqraftProviderProps, 'waitForStorage'> {
14
31
  children: ReactNode;
@@ -53,26 +70,35 @@ export const ReqraftUserProvider = ({ children, waitForStorage }: IReqraftStorag
53
70
  includeAppPrefix: boolean = true
54
71
  ) {
55
72
  const _path = includeAppPrefix ? `${appName}.${path}` : path;
56
- const updatedStorage = set(cloneDeep(currentUser?.storage), _path, value);
73
+ // Base the write on the LATEST storage read straight from the store, not
74
+ // the blob captured in this callback's closure. A caller holding a stale
75
+ // updater — e.g. an imperative store subscription created on mount, before
76
+ // other keys had loaded — would otherwise persist an out-of-date blob and
77
+ // wipe every key written to storage since its closure was captured.
78
+ const latestStorage = currentUserStore.getState().currentUser?.storage;
79
+ const updatedStorage = applyStorageWrite(latestStorage, _path, value);
57
80
 
58
81
  updateCurrentUserStorage(updatedStorage);
59
82
 
60
83
  load({ body: { storage: updatedStorage } });
61
84
  },
62
- [appName, currentUser?.storage, load, updateCurrentUserStorage]
85
+ [appName, load, updateCurrentUserStorage]
63
86
  );
64
87
 
65
88
  const removeStorageValue = useCallback(
66
89
  function (path: string, includeAppPrefix: boolean = true) {
67
90
  const _path = includeAppPrefix ? `${appName}.${path}` : path;
68
91
 
69
- const updatedStorage = set(cloneDeep(currentUser?.storage), _path, null);
92
+ // Same as `updateStorage`: mutate the LATEST blob (never a stale closure
93
+ // copy), seeding a fresh object when there's no storage yet.
94
+ const latestStorage = currentUserStore.getState().currentUser?.storage;
95
+ const updatedStorage = applyStorageWrite(latestStorage, _path, null);
70
96
 
71
97
  updateCurrentUserStorage(updatedStorage);
72
98
 
73
99
  load({ body: { storage_path: _path } });
74
100
  },
75
- [appName, currentUser?.storage, load, updateCurrentUserStorage]
101
+ [appName, load, updateCurrentUserStorage]
76
102
  );
77
103
 
78
104
  const contextValue = useMemo(
@@ -6,6 +6,38 @@ import { currentUserStore } from './currentUser';
6
6
 
7
7
  const meta = {
8
8
  title: 'Stores/Current User',
9
+ parameters: {
10
+ mockData: [
11
+ {
12
+ url: 'https://hq.qoretechnologies.com:8092/api/latest/users?action=current',
13
+ method: 'GET',
14
+ status: 200,
15
+ response: {
16
+ provider: 'local',
17
+ username: 'david',
18
+ name: 'David Nichols',
19
+ has_default: true,
20
+ roles: ['admin'],
21
+ permissions: ['USER-CONTROL'],
22
+ workflows: [],
23
+ services: [],
24
+ jobs: [],
25
+ mappers: [],
26
+ vmaps: [],
27
+ groups: [],
28
+ fsms: [],
29
+ },
30
+ },
31
+ ],
32
+ },
33
+ beforeEach: () => {
34
+ currentUserStore.setState({
35
+ currentUser: undefined,
36
+ loading: false,
37
+ error: undefined,
38
+ errorData: undefined,
39
+ });
40
+ },
9
41
  render: () => {
10
42
  const { load, loading, currentUser = {}, errorData } = currentUserStore();
11
43
 
@@ -26,6 +58,14 @@ export default meta;
26
58
  export type Story = StoryObj<typeof meta>;
27
59
 
28
60
  export const CurrentUserCanBeLoaded: Story = {
61
+ parameters: {
62
+ docs: {
63
+ description: {
64
+ story:
65
+ 'Renders a demo that reads currentUserStore — the store loads /users?action=current and the returned user object is displayed as a tree.',
66
+ },
67
+ },
68
+ },
29
69
  play: async () => {
30
70
  await testsWaitForText('"David Nichols"');
31
71
  },
@@ -33,6 +73,15 @@ export const CurrentUserCanBeLoaded: Story = {
33
73
 
34
74
  export const CurrentUserHasPermissions: Story = {
35
75
  ...CurrentUserCanBeLoaded,
76
+ parameters: {
77
+ ...CurrentUserCanBeLoaded.parameters,
78
+ docs: {
79
+ description: {
80
+ story:
81
+ 'Renders a demo that calls currentUserStore().hasAnyPermission — the affirmative message renders because the loaded user carries at least one of the requested permissions.',
82
+ },
83
+ },
84
+ },
36
85
  render: () => {
37
86
  const { hasAnyPermission } = currentUserStore();
38
87