@stack-spot/portal-layout 0.0.65 → 1.0.0-dev.1768482785050

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 (363) hide show
  1. package/CHANGELOG.md +779 -0
  2. package/dist/Layout.d.ts +60 -8
  3. package/dist/Layout.d.ts.map +1 -1
  4. package/dist/Layout.js +59 -24
  5. package/dist/Layout.js.map +1 -1
  6. package/dist/LayoutOverlayManager.d.ts +274 -19
  7. package/dist/LayoutOverlayManager.d.ts.map +1 -1
  8. package/dist/LayoutOverlayManager.js +373 -82
  9. package/dist/LayoutOverlayManager.js.map +1 -1
  10. package/dist/WelcomeTour.d.ts +2 -0
  11. package/dist/WelcomeTour.d.ts.map +1 -0
  12. package/dist/WelcomeTour.js +8 -0
  13. package/dist/WelcomeTour.js.map +1 -0
  14. package/dist/components/Backdrop.d.ts +75 -0
  15. package/dist/components/Backdrop.d.ts.map +1 -0
  16. package/dist/components/Backdrop.js +69 -0
  17. package/dist/components/Backdrop.js.map +1 -0
  18. package/dist/components/Contact/show-contact-modal.d.ts +5 -0
  19. package/dist/components/Contact/show-contact-modal.d.ts.map +1 -0
  20. package/dist/components/Contact/show-contact-modal.js +37 -0
  21. package/dist/components/Contact/show-contact-modal.js.map +1 -0
  22. package/dist/components/ContactModal.d.ts +1 -0
  23. package/dist/components/ContactModal.d.ts.map +1 -0
  24. package/dist/components/ContactModal.js +2 -0
  25. package/dist/components/ContactModal.js.map +1 -0
  26. package/dist/components/Dialog.d.ts +54 -7
  27. package/dist/components/Dialog.d.ts.map +1 -1
  28. package/dist/components/Dialog.js +8 -2
  29. package/dist/components/Dialog.js.map +1 -1
  30. package/dist/components/Header.d.ts +38 -2
  31. package/dist/components/Header.d.ts.map +1 -1
  32. package/dist/components/Header.js +9 -4
  33. package/dist/components/Header.js.map +1 -1
  34. package/dist/components/NotificationCenter/NotificationPanel.d.ts +3 -0
  35. package/dist/components/NotificationCenter/NotificationPanel.d.ts.map +1 -0
  36. package/dist/components/NotificationCenter/NotificationPanel.js +19 -0
  37. package/dist/components/NotificationCenter/NotificationPanel.js.map +1 -0
  38. package/dist/components/NotificationCenter/NotificationPanelHeader.d.ts +3 -0
  39. package/dist/components/NotificationCenter/NotificationPanelHeader.d.ts.map +1 -0
  40. package/dist/components/NotificationCenter/NotificationPanelHeader.js +16 -0
  41. package/dist/components/NotificationCenter/NotificationPanelHeader.js.map +1 -0
  42. package/dist/components/NotificationCenter/NotificationsPanelFooter.d.ts +4 -0
  43. package/dist/components/NotificationCenter/NotificationsPanelFooter.d.ts.map +1 -0
  44. package/dist/components/NotificationCenter/NotificationsPanelFooter.js +12 -0
  45. package/dist/components/NotificationCenter/NotificationsPanelFooter.js.map +1 -0
  46. package/dist/components/NotificationCenter/dictionary.d.ts +2 -0
  47. package/dist/components/NotificationCenter/dictionary.d.ts.map +1 -0
  48. package/dist/components/NotificationCenter/dictionary.js +43 -0
  49. package/dist/components/NotificationCenter/dictionary.js.map +1 -0
  50. package/dist/components/NotificationCenter/index.d.ts +2 -0
  51. package/dist/components/NotificationCenter/index.d.ts.map +1 -0
  52. package/dist/components/NotificationCenter/index.js +34 -0
  53. package/dist/components/NotificationCenter/index.js.map +1 -0
  54. package/dist/components/NotificationCenter/styled.d.ts +3 -0
  55. package/dist/components/NotificationCenter/styled.d.ts.map +1 -0
  56. package/dist/components/NotificationCenter/styled.js +74 -0
  57. package/dist/components/NotificationCenter/styled.js.map +1 -0
  58. package/dist/components/NotificationCenter/types.d.ts +21 -0
  59. package/dist/components/NotificationCenter/types.d.ts.map +1 -0
  60. package/dist/components/NotificationCenter/types.js.map +1 -0
  61. package/dist/components/NotificationCenter/utils.d.ts +5 -0
  62. package/dist/components/NotificationCenter/utils.d.ts.map +1 -0
  63. package/dist/components/NotificationCenter/utils.js +18 -0
  64. package/dist/components/NotificationCenter/utils.js.map +1 -0
  65. package/dist/components/OverlayContent.d.ts +27 -1
  66. package/dist/components/OverlayContent.d.ts.map +1 -1
  67. package/dist/components/OverlayContent.js +8 -4
  68. package/dist/components/OverlayContent.js.map +1 -1
  69. package/dist/components/PortalSwitcher.d.ts +19 -1
  70. package/dist/components/PortalSwitcher.d.ts.map +1 -1
  71. package/dist/components/PortalSwitcher.js +16 -31
  72. package/dist/components/PortalSwitcher.js.map +1 -1
  73. package/dist/components/PrivacyPolicyMessage/hooks.d.ts +10 -0
  74. package/dist/components/PrivacyPolicyMessage/hooks.d.ts.map +1 -0
  75. package/dist/components/PrivacyPolicyMessage/hooks.js +33 -0
  76. package/dist/components/PrivacyPolicyMessage/hooks.js.map +1 -0
  77. package/dist/components/PrivacyPolicyMessage/index.d.ts +7 -0
  78. package/dist/components/PrivacyPolicyMessage/index.d.ts.map +1 -0
  79. package/dist/components/PrivacyPolicyMessage/index.js +5 -0
  80. package/dist/components/PrivacyPolicyMessage/index.js.map +1 -0
  81. package/dist/components/Rate/FeedbackModal.d.ts +8 -0
  82. package/dist/components/Rate/FeedbackModal.d.ts.map +1 -0
  83. package/dist/components/Rate/FeedbackModal.js +52 -0
  84. package/dist/components/Rate/FeedbackModal.js.map +1 -0
  85. package/dist/components/Rate/hook.d.ts +3 -0
  86. package/dist/components/Rate/hook.d.ts.map +1 -0
  87. package/dist/components/Rate/hook.js +48 -0
  88. package/dist/components/Rate/hook.js.map +1 -0
  89. package/dist/components/Rate/index.d.ts +10 -0
  90. package/dist/components/Rate/index.d.ts.map +1 -0
  91. package/dist/components/Rate/index.js +16 -0
  92. package/dist/components/Rate/index.js.map +1 -0
  93. package/dist/components/Rate/on-nps-submit.d.ts +7 -0
  94. package/dist/components/Rate/on-nps-submit.d.ts.map +1 -0
  95. package/dist/components/Rate/on-nps-submit.js +8 -0
  96. package/dist/components/Rate/on-nps-submit.js.map +1 -0
  97. package/dist/components/Rate/show-rate-us-modals.d.ts +17 -0
  98. package/dist/components/Rate/show-rate-us-modals.d.ts.map +1 -0
  99. package/dist/components/Rate/show-rate-us-modals.js +14 -0
  100. package/dist/components/Rate/show-rate-us-modals.js.map +1 -0
  101. package/dist/components/Rate/utils.d.ts +2 -0
  102. package/dist/components/Rate/utils.d.ts.map +1 -0
  103. package/dist/components/Rate/utils.js +10 -0
  104. package/dist/components/Rate/utils.js.map +1 -0
  105. package/dist/components/Toaster.d.ts +35 -0
  106. package/dist/components/Toaster.d.ts.map +1 -1
  107. package/dist/components/Toaster.js +32 -4
  108. package/dist/components/Toaster.js.map +1 -1
  109. package/dist/components/TypeForm/hook.d.ts +2 -0
  110. package/dist/components/TypeForm/hook.d.ts.map +1 -0
  111. package/dist/components/TypeForm/hook.js +11 -0
  112. package/dist/components/TypeForm/hook.js.map +1 -0
  113. package/dist/components/TypeForm/index.d.ts +6 -0
  114. package/dist/components/TypeForm/index.d.ts.map +1 -0
  115. package/dist/components/TypeForm/index.js +23 -0
  116. package/dist/components/TypeForm/index.js.map +1 -0
  117. package/dist/components/TypeForm/show-typeform-modal.d.ts +5 -0
  118. package/dist/components/TypeForm/show-typeform-modal.d.ts.map +1 -0
  119. package/dist/components/TypeForm/show-typeform-modal.js +11 -0
  120. package/dist/components/TypeForm/show-typeform-modal.js.map +1 -0
  121. package/dist/components/TypeForm/utils.d.ts +2 -0
  122. package/dist/components/TypeForm/utils.d.ts.map +1 -0
  123. package/dist/components/TypeForm/utils.js +9 -0
  124. package/dist/components/TypeForm/utils.js.map +1 -0
  125. package/dist/components/UserMenu.d.ts +19 -2
  126. package/dist/components/UserMenu.d.ts.map +1 -1
  127. package/dist/components/UserMenu.js +12 -6
  128. package/dist/components/UserMenu.js.map +1 -1
  129. package/dist/components/error/ErrorBoundary.d.ts +25 -4
  130. package/dist/components/error/ErrorBoundary.d.ts.map +1 -1
  131. package/dist/components/error/ErrorBoundary.js +10 -1
  132. package/dist/components/error/ErrorBoundary.js.map +1 -1
  133. package/dist/components/error/ErrorManager.d.ts +22 -6
  134. package/dist/components/error/ErrorManager.d.ts.map +1 -1
  135. package/dist/components/error/ErrorManager.js +21 -1
  136. package/dist/components/error/ErrorManager.js.map +1 -1
  137. package/dist/components/error/SilentErrorBoundary.d.ts +26 -5
  138. package/dist/components/error/SilentErrorBoundary.d.ts.map +1 -1
  139. package/dist/components/error/SilentErrorBoundary.js +10 -0
  140. package/dist/components/error/SilentErrorBoundary.js.map +1 -1
  141. package/dist/components/menu/MenuContent.d.ts +20 -4
  142. package/dist/components/menu/MenuContent.d.ts.map +1 -1
  143. package/dist/components/menu/MenuContent.js +89 -88
  144. package/dist/components/menu/MenuContent.js.map +1 -1
  145. package/dist/components/menu/MenuSectionGroup.d.ts +2 -0
  146. package/dist/components/menu/MenuSectionGroup.d.ts.map +1 -0
  147. package/dist/components/menu/MenuSectionGroup.js +121 -0
  148. package/dist/components/menu/MenuSectionGroup.js.map +1 -0
  149. package/dist/components/menu/MenuSections.d.ts +17 -0
  150. package/dist/components/menu/MenuSections.d.ts.map +1 -1
  151. package/dist/components/menu/MenuSections.js +159 -39
  152. package/dist/components/menu/MenuSections.js.map +1 -1
  153. package/dist/components/menu/PageSelector.d.ts +5 -0
  154. package/dist/components/menu/PageSelector.d.ts.map +1 -1
  155. package/dist/components/menu/PageSelector.js +9 -4
  156. package/dist/components/menu/PageSelector.js.map +1 -1
  157. package/dist/components/menu/types.d.ts +219 -8
  158. package/dist/components/menu/types.d.ts.map +1 -1
  159. package/dist/components/tour/StepContainer.d.ts +37 -0
  160. package/dist/components/tour/StepContainer.d.ts.map +1 -0
  161. package/dist/components/tour/StepContainer.js +51 -0
  162. package/dist/components/tour/StepContainer.js.map +1 -0
  163. package/dist/components/tour/StepNavigation.d.ts +29 -0
  164. package/dist/components/tour/StepNavigation.d.ts.map +1 -0
  165. package/dist/components/tour/StepNavigation.js +37 -0
  166. package/dist/components/tour/StepNavigation.js.map +1 -0
  167. package/dist/components/tour/StepTitle.d.ts +17 -0
  168. package/dist/components/tour/StepTitle.d.ts.map +1 -0
  169. package/dist/components/tour/StepTitle.js +10 -0
  170. package/dist/components/tour/StepTitle.js.map +1 -0
  171. package/dist/components/tour/hook.d.ts +3 -0
  172. package/dist/components/tour/hook.d.ts.map +1 -0
  173. package/dist/components/tour/hook.js +10 -0
  174. package/dist/components/tour/hook.js.map +1 -0
  175. package/dist/components/tour/index.d.ts +5 -0
  176. package/dist/components/tour/index.d.ts.map +1 -0
  177. package/dist/components/tour/index.js +5 -0
  178. package/dist/components/tour/index.js.map +1 -0
  179. package/dist/components/tour/manager.d.ts +34 -0
  180. package/dist/components/tour/manager.d.ts.map +1 -0
  181. package/dist/components/tour/manager.js +104 -0
  182. package/dist/components/tour/manager.js.map +1 -0
  183. package/dist/components/tour/utils.d.ts +67 -0
  184. package/dist/components/tour/utils.d.ts.map +1 -0
  185. package/dist/components/tour/utils.js +60 -0
  186. package/dist/components/tour/utils.js.map +1 -0
  187. package/dist/components/user-menu-manager.d.ts +13 -0
  188. package/dist/components/user-menu-manager.d.ts.map +1 -0
  189. package/dist/components/user-menu-manager.js +36 -0
  190. package/dist/components/user-menu-manager.js.map +1 -0
  191. package/dist/dictionary.d.ts +6 -1
  192. package/dist/dictionary.d.ts.map +1 -1
  193. package/dist/dictionary.js +7 -2
  194. package/dist/dictionary.js.map +1 -1
  195. package/dist/elements.d.ts +7 -0
  196. package/dist/elements.d.ts.map +1 -1
  197. package/dist/elements.js +7 -0
  198. package/dist/elements.js.map +1 -1
  199. package/dist/index.d.ts +15 -8
  200. package/dist/index.d.ts.map +1 -1
  201. package/dist/index.js +15 -8
  202. package/dist/index.js.map +1 -1
  203. package/dist/layout.css +190 -35
  204. package/dist/svg/StarFillWithGradient.d.ts +6 -0
  205. package/dist/svg/StarFillWithGradient.d.ts.map +1 -0
  206. package/dist/svg/StarFillWithGradient.js +4 -0
  207. package/dist/svg/StarFillWithGradient.js.map +1 -0
  208. package/dist/toaster.d.ts +55 -9
  209. package/dist/toaster.d.ts.map +1 -1
  210. package/dist/toaster.js +34 -6
  211. package/dist/toaster.js.map +1 -1
  212. package/dist/types.d.ts +5 -0
  213. package/dist/types.d.ts.map +1 -0
  214. package/dist/types.js +2 -0
  215. package/dist/types.js.map +1 -0
  216. package/dist/utils.d.ts +6 -69
  217. package/dist/utils.d.ts.map +1 -1
  218. package/dist/utils.js +9 -130
  219. package/dist/utils.js.map +1 -1
  220. package/package.dev.json +3 -0
  221. package/package.json +24 -13
  222. package/package.stg.json +3 -0
  223. package/readme.md +147 -0
  224. package/src/Layout.tsx +166 -58
  225. package/src/LayoutOverlayManager.tsx +499 -85
  226. package/src/WelcomeTour.tsx +8 -0
  227. package/src/components/Backdrop.tsx +116 -0
  228. package/src/components/Contact/show-contact-modal.tsx +71 -0
  229. package/src/components/Dialog.tsx +58 -9
  230. package/src/components/Header.tsx +53 -5
  231. package/src/components/NotificationCenter/NotificationPanel.tsx +40 -0
  232. package/src/components/NotificationCenter/NotificationPanelHeader.tsx +53 -0
  233. package/src/components/NotificationCenter/NotificationsPanelFooter.tsx +25 -0
  234. package/src/components/NotificationCenter/dictionary.ts +44 -0
  235. package/src/components/NotificationCenter/index.tsx +58 -0
  236. package/src/components/NotificationCenter/styled.ts +75 -0
  237. package/src/components/NotificationCenter/types.ts +24 -0
  238. package/src/components/NotificationCenter/utils.ts +20 -0
  239. package/src/components/OverlayContent.tsx +40 -5
  240. package/src/components/PortalSwitcher.tsx +33 -39
  241. package/src/components/PrivacyPolicyMessage/hooks.tsx +49 -0
  242. package/src/components/PrivacyPolicyMessage/index.tsx +21 -0
  243. package/src/components/Rate/FeedbackModal.tsx +86 -0
  244. package/src/components/Rate/hook.tsx +61 -0
  245. package/src/components/Rate/index.tsx +36 -0
  246. package/src/components/Rate/on-nps-submit.ts +18 -0
  247. package/src/components/Rate/show-rate-us-modals.tsx +29 -0
  248. package/src/components/Rate/utils.ts +11 -0
  249. package/src/components/Toaster.tsx +82 -3
  250. package/src/components/TypeForm/hook.tsx +13 -0
  251. package/src/components/TypeForm/index.tsx +50 -0
  252. package/src/components/TypeForm/show-typeform-modal.tsx +10 -0
  253. package/src/components/TypeForm/utils.ts +8 -0
  254. package/src/components/UserMenu.tsx +32 -8
  255. package/src/components/error/ErrorBoundary.tsx +11 -2
  256. package/src/components/error/ErrorManager.ts +22 -6
  257. package/src/components/error/SilentErrorBoundary.tsx +12 -2
  258. package/src/components/menu/MenuContent.tsx +102 -110
  259. package/src/components/menu/MenuSectionGroup.tsx +121 -0
  260. package/src/components/menu/MenuSections.tsx +342 -93
  261. package/src/components/menu/PageSelector.tsx +16 -4
  262. package/src/components/menu/types.ts +221 -9
  263. package/src/components/tour/StepContainer.tsx +92 -0
  264. package/src/components/tour/StepNavigation.tsx +72 -0
  265. package/src/components/tour/StepTitle.tsx +28 -0
  266. package/src/components/tour/hook.ts +12 -0
  267. package/src/components/tour/index.ts +6 -0
  268. package/src/components/tour/manager.tsx +119 -0
  269. package/src/components/tour/utils.tsx +119 -0
  270. package/src/components/user-menu-manager.ts +31 -0
  271. package/src/dictionary.ts +7 -2
  272. package/src/elements.ts +7 -0
  273. package/src/index.ts +15 -8
  274. package/src/layout.css +190 -35
  275. package/src/svg/StarFillWithGradient.tsx +14 -0
  276. package/src/toaster.tsx +90 -13
  277. package/src/types.ts +4 -0
  278. package/src/utils.ts +9 -142
  279. package/dist/components/BottomNotification.d.ts +0 -1
  280. package/dist/components/BottomNotification.d.ts.map +0 -1
  281. package/dist/components/BottomNotification.js +0 -2
  282. package/dist/components/BottomNotification.js.map +0 -1
  283. package/dist/components/BottomPanel.d.ts +0 -1
  284. package/dist/components/BottomPanel.d.ts.map +0 -1
  285. package/dist/components/BottomPanel.js +0 -2
  286. package/dist/components/BottomPanel.js.map +0 -1
  287. package/dist/components/SelectionList.d.ts +0 -36
  288. package/dist/components/SelectionList.d.ts.map +0 -1
  289. package/dist/components/SelectionList.js +0 -140
  290. package/dist/components/SelectionList.js.map +0 -1
  291. package/dist/components/error/ErrorFeedback.d.ts +0 -3
  292. package/dist/components/error/ErrorFeedback.d.ts.map +0 -1
  293. package/dist/components/error/ErrorFeedback.js +0 -66
  294. package/dist/components/error/ErrorFeedback.js.map +0 -1
  295. package/dist/components/menu/use-check-text-overflow.d.ts +0 -6
  296. package/dist/components/menu/use-check-text-overflow.d.ts.map +0 -1
  297. package/dist/components/menu/use-check-text-overflow.js +0 -20
  298. package/dist/components/menu/use-check-text-overflow.js.map +0 -1
  299. package/dist/components/menu/use-keyboard-controls.d.ts +0 -23
  300. package/dist/components/menu/use-keyboard-controls.d.ts.map +0 -1
  301. package/dist/components/menu/use-keyboard-controls.js +0 -49
  302. package/dist/components/menu/use-keyboard-controls.js.map +0 -1
  303. package/dist/components/tour/PortalSwitcherStep.d.ts +0 -3
  304. package/dist/components/tour/PortalSwitcherStep.d.ts.map +0 -1
  305. package/dist/components/tour/PortalSwitcherStep.js +0 -29
  306. package/dist/components/tour/PortalSwitcherStep.js.map +0 -1
  307. package/dist/components/types.d.ts +0 -15
  308. package/dist/components/types.d.ts.map +0 -1
  309. package/dist/components/types.js.map +0 -1
  310. package/dist/layout-context.d.ts +0 -10
  311. package/dist/layout-context.d.ts.map +0 -1
  312. package/dist/layout-context.js +0 -11
  313. package/dist/layout-context.js.map +0 -1
  314. package/dist/svg/AI.d.ts +0 -6
  315. package/dist/svg/AI.d.ts.map +0 -1
  316. package/dist/svg/AI.js +0 -9
  317. package/dist/svg/AI.js.map +0 -1
  318. package/dist/svg/EDP.d.ts +0 -6
  319. package/dist/svg/EDP.d.ts.map +0 -1
  320. package/dist/svg/EDP.js +0 -5
  321. package/dist/svg/EDP.js.map +0 -1
  322. package/dist/svg/Forbidden.d.ts +0 -6
  323. package/dist/svg/Forbidden.d.ts.map +0 -1
  324. package/dist/svg/Forbidden.js +0 -4
  325. package/dist/svg/Forbidden.js.map +0 -1
  326. package/dist/svg/HUB.d.ts +0 -6
  327. package/dist/svg/HUB.d.ts.map +0 -1
  328. package/dist/svg/HUB.js +0 -5
  329. package/dist/svg/HUB.js.map +0 -1
  330. package/dist/svg/Logo.d.ts +0 -2
  331. package/dist/svg/Logo.d.ts.map +0 -1
  332. package/dist/svg/Logo.js +0 -4
  333. package/dist/svg/Logo.js.map +0 -1
  334. package/dist/svg/NotFound.d.ts +0 -6
  335. package/dist/svg/NotFound.d.ts.map +0 -1
  336. package/dist/svg/NotFound.js +0 -4
  337. package/dist/svg/NotFound.js.map +0 -1
  338. package/dist/svg/ServerError.d.ts +0 -6
  339. package/dist/svg/ServerError.d.ts.map +0 -1
  340. package/dist/svg/ServerError.js +0 -4
  341. package/dist/svg/ServerError.js.map +0 -1
  342. package/dist/svg/Unauthenticated.d.ts +0 -6
  343. package/dist/svg/Unauthenticated.d.ts.map +0 -1
  344. package/dist/svg/Unauthenticated.js +0 -4
  345. package/dist/svg/Unauthenticated.js.map +0 -1
  346. package/src/components/BottomPanel.tsx +0 -0
  347. package/src/components/SelectionList.tsx +0 -272
  348. package/src/components/error/ErrorFeedback.tsx +0 -114
  349. package/src/components/menu/use-check-text-overflow.tsx +0 -26
  350. package/src/components/menu/use-keyboard-controls.tsx +0 -70
  351. package/src/components/tour/PortalSwitcherStep.tsx +0 -36
  352. package/src/components/types.ts +0 -15
  353. package/src/layout-context.tsx +0 -22
  354. package/src/svg/AI.tsx +0 -37
  355. package/src/svg/EDP.tsx +0 -35
  356. package/src/svg/Forbidden.tsx +0 -22
  357. package/src/svg/HUB.tsx +0 -35
  358. package/src/svg/Logo.tsx +0 -35
  359. package/src/svg/NotFound.tsx +0 -16
  360. package/src/svg/ServerError.tsx +0 -33
  361. package/src/svg/Unauthenticated.tsx +0 -16
  362. /package/dist/components/{types.js → NotificationCenter/types.js} +0 -0
  363. /package/src/components/{BottomNotification.tsx → ContactModal.tsx} +0 -0
@@ -1,32 +1,62 @@
1
- import { Flex, IconBox, Text } from '@citric/core'
2
- import { ChevronRight, Cog, Collapse, Expand } from '@citric/icons'
3
- import { Dictionary, interpolate, useTranslate } from '@stack-spot/portal-translate'
4
- import { useCallback, useMemo, useState } from 'react'
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ // todo: remove unused code as soon as this is validated by the design team: "remove overlays on hover".
3
+ import { Box, Flex, IconBox, Text } from '@citric/core'
4
+ import { ChevronUpFill, Cog, Collapse, Expand, Support } from '@citric/icons'
5
+ import { ReactTooltip } from '@citric/ui'
6
+ import { useKeyboardControls } from '@stack-spot/portal-components'
7
+ import { useAnchorTag } from '@stack-spot/portal-components/anchor'
8
+ import { listToClass } from '@stack-spot/portal-theme'
9
+ import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
10
+ import { useCallback, useEffect, useMemo, useState } from 'react'
5
11
  import { elementIds, getLayoutElements } from '../../elements'
6
- import { useAnchorTag } from '../../layout-context'
12
+ import { StarFillWithGradient } from '../../svg/StarFillWithGradient'
13
+ import { isLessThan30Days } from '../Rate/hook'
7
14
  import { MenuContent } from './MenuContent'
15
+ import { MenuSectionGroup } from './MenuSectionGroup'
8
16
  import { MenuProps, MenuSection } from './types'
9
- import { useKeyboardControls } from './use-keyboard-controls'
10
17
 
18
+ /**
19
+ * Amount of time to wait before hiding the menu overlay once the mouse leaves its area.
20
+ */
11
21
  const HIDE_OVERLAY_DELAY_MS = 400
12
22
  const MENU_OVERLAY_ID = 'menuContentOverlay'
13
23
 
24
+ /**
25
+ * Pointer to the latest "hideOverlay" task. This allows the operation to be cancelled.
26
+ */
14
27
  let hideOverlayTask: number | undefined
15
28
 
16
- // fixme: this should definitely not be handled like this...
29
+ /**
30
+ * Accessibility. Makes the menu overlay accessible through the keyboard.
31
+ */
17
32
  let attachKeyboardListenersForOverlay: () => void
33
+ /**
34
+ * Accessibility. Makes the menu overlay invisible to the keyboard.
35
+ */
18
36
  let detachKeyboardListenersForOverlay: () => void
19
37
 
38
+ /**
39
+ * Hides the menu overlay after HIDE_OVERLAY_DELAY_MS. This operation may be canceled.
40
+ *
41
+ * This gives the user some time to move the mouse outside the overlay for while before it disappears. If the user moves the mouse out of
42
+ * the overlay and, before HIDE_OVERLAY_DELAY_MS, moves the mouse back, we want the overlay to keep showing.
43
+ */
44
+ // @ts-ignore: unused (remove when validated by the design team)
20
45
  function hideOverlay() {
21
46
  if (hideOverlayTask !== undefined) return
22
47
  hideOverlayTask = window.setTimeout(hideOverlayImmediately, HIDE_OVERLAY_DELAY_MS)
23
48
  }
24
49
 
50
+ /**
51
+ * Accessibility. Returns the accessibility button of the section with the preview (overlay) currently active.
52
+ */
25
53
  function getAccessibilityButtonOfSectionWithActiveOverlay(): HTMLElement | null | undefined {
26
54
  return document.getElementById(elementIds.menuSections)?.querySelector('button[aria-expanded="true"]')
27
55
  }
28
56
 
29
- // eslint-disable-next-line react-refresh/only-export-components
57
+ /**
58
+ * Hides the menu overlay without waiting for anything. This is not cancellable.
59
+ */
30
60
  export function hideOverlayImmediately() {
31
61
  detachKeyboardListenersForOverlay?.()
32
62
  const overlay = document.getElementById(MENU_OVERLAY_ID)
@@ -36,12 +66,18 @@ export function hideOverlayImmediately() {
36
66
  getAccessibilityButtonOfSectionWithActiveOverlay()?.setAttribute('aria-expanded', 'false')
37
67
  }
38
68
 
69
+ /**
70
+ * If `hideOverlay` was called and not fulfilled yet, this cancels the task, preventing the overlay from closing.
71
+ */
39
72
  function cancelHideOverlayTask() {
40
73
  if (hideOverlayTask === undefined) return
41
74
  clearTimeout(hideOverlayTask)
42
75
  hideOverlayTask = undefined
43
76
  }
44
77
 
78
+ /**
79
+ * Shows the menu overlay.
80
+ */
45
81
  function showOverlay() {
46
82
  cancelHideOverlayTask()
47
83
  const overlay = document.getElementById(MENU_OVERLAY_ID)
@@ -51,10 +87,23 @@ function showOverlay() {
51
87
  attachKeyboardListenersForOverlay?.()
52
88
  }
53
89
 
90
+ /**
91
+ * Checks if the menu content (2nd menu list from left to right) is visible or not.
92
+ * The menu content is visible if:
93
+ * 1. The menu is not compact (the button at the end of the section list compacts/expands the menu);
94
+ * 2. If the current section has any menu content.
95
+ */
54
96
  function isMenuContentVisible() {
55
- return !!document.getElementById('layout')?.classList?.contains('menu-content-visible')
97
+ return !!document.getElementById(elementIds.layout)?.classList?.contains('menu-content-visible')
56
98
  }
57
99
 
100
+ /**
101
+ * A section in the the menu-sections.
102
+ *
103
+ * A section in the menu is responsible for rendering the section icon, label, accessibility button and controlling the menu overlay.
104
+ * @param props React props for the component {@link MenuSection} & { id, setCurrentOverlay }. Id identifies the current section and
105
+ * setCurrentOverlay controls the overlay (preview) content.
106
+ */
58
107
  const Section = ({
59
108
  icon,
60
109
  label,
@@ -64,31 +113,32 @@ const Section = ({
64
113
  active,
65
114
  content,
66
115
  customContent,
67
- onOpen,
68
116
  setCurrentOverlay,
69
117
  id,
70
- hasContent,
71
- className,
118
+ badge,
72
119
  }: MenuSection & {
73
- id: number, setCurrentOverlay: (id: number | undefined) => void, hasContent: boolean,
120
+ id: number,
121
+ setCurrentOverlay: (id: number | undefined) => void,
122
+ hasContent: boolean,
74
123
  }) => {
75
124
  const Link = useAnchorTag()
125
+ // const t = useTranslate(dictionary)
76
126
  const contentToRender = typeof content === 'function' ? content() : content
77
- const t = useTranslate(dictionary)
127
+
78
128
  function shouldShowOverlay() {
79
129
  /* The overlay should appear if:
80
- * 1. The menu is compacted showing only the icons
130
+ * 1. The menu is compacted showing only the icons
81
131
  * 2. The section has some content to render OR:
82
- * 3. The section is active and there is a contextual menu for the active page.
132
+ * 3. There is a contextual menu for the active page.
83
133
  */
84
134
  const { layout } = getLayoutElements()
85
135
  const isCompactedOnlyIcons = layout?.classList.contains('menu-compact')
86
- return isCompactedOnlyIcons && (!!contentToRender || !!customContent || (hasContent && active))
136
+ return isCompactedOnlyIcons && (!!contentToRender || !!customContent)
87
137
  }
88
138
 
139
+ // @ts-ignore: unused (remove when validated by the design team)
89
140
  function prepareShowOverlay(event: React.MouseEvent<HTMLAnchorElement, MouseEvent> | React.KeyboardEvent<any>) {
90
141
  if (!shouldShowOverlay()) return
91
- onOpen?.()
92
142
  const anchorElement = event.target as HTMLElement
93
143
  const accessibilityButton = anchorElement?.parentElement?.querySelector('button') as HTMLElement
94
144
  accessibilityButton?.setAttribute('aria-expanded', 'true')
@@ -99,94 +149,162 @@ const Section = ({
99
149
  function click() {
100
150
  if (onClick) onClick()
101
151
  hideOverlayImmediately()
152
+ const classList = document.getElementById(elementIds.layout)?.classList
153
+ if (!classList) return
154
+ classList.remove('menu-manual')
102
155
  }
103
156
 
104
157
  const labelText = typeof label === 'string' ? label : label.id
105
158
 
106
159
  return (
107
160
  <>
108
- <li
109
- role="menuitem"
110
- key={labelText}
111
- title={labelText}
112
- className={`section-submenu ${className || ''} ${active ? 'active' : ''}`}
113
- aria-selected={active}>
114
- <Link
115
- href={href}
116
- target={target}
117
- onClick={click}
118
- onMouseEnter={prepareShowOverlay}
119
- onMouseLeave={() => shouldShowOverlay() && hideOverlay()}
120
- title={labelText}
121
- aria-label={labelText}
122
- onKeyDown={onClick ? e => e.key === 'Enter' && onClick() : undefined}
123
- {...(active ? { 'aria-current': 'page' } : undefined)}
124
- {...(!href ? { 'tabIndex': 0 } : undefined)}
161
+ <Link
162
+ href={href}
163
+ target={target}
164
+ onClick={click}
165
+ // onMouseEnter={prepareShowOverlay}
166
+ // onMouseLeave={() => shouldShowOverlay() && hideOverlay()}
167
+ aria-label={labelText}
168
+ onKeyDown={onClick ? e => e.key === 'Enter' && onClick() : undefined}
169
+ {...(active ? { 'aria-current': 'page' } : undefined)}
170
+ {...(!href ? { 'tabIndex': 0 } : undefined)}
171
+ >
172
+ <ReactTooltip
173
+ text={labelText}
174
+ position="right"
175
+ tabIndex={-1}
176
+ target={document.getElementById(elementIds.layout)}
177
+ disableTooltip={() => !document.getElementById(elementIds.layout)}
178
+ sx={{ width: 'inherit' }}
125
179
  >
126
- <Flex alignItems="center" justifyContent="center" px={5}>
127
- <IconBox>{icon}</IconBox>
128
- {typeof label === 'string' ? <Text appearance="microtext1" className="section-label" ml={3}>{label}</Text> : label.element}
180
+ <Flex alignItems="center" justifyContent="center" flexWrap="nowrap" px={3} w="100%">
181
+ <IconBox className={badge ? 'section-icon-with-badge' : 'section-icon'}>
182
+ {icon}
183
+ </IconBox>
184
+ <Flex alignItems="center" justifyContent="space-between" w="100%" className="section-text">
185
+ {typeof label === 'string' ? (
186
+ <Text appearance="microtext1" className="section-label" ml={3}>
187
+ {label}
188
+ </Text>
189
+ ) : (label.element)}
190
+ {badge && <Box className="section-badge">{badge}</Box>}
191
+ </Flex>
129
192
  </Flex>
130
- </Link>
131
- {shouldShowOverlay() &&
132
- <IconBox size="sm" className="section-submenu-icon"
133
- as="button"
134
- aria-label={interpolate(t.menuOptions, label)}
135
- aria-controls={MENU_OVERLAY_ID}
136
- aria-expanded={false}
137
- onKeyDown={(event) => {
138
- if (event.key === 'Enter') {
139
- prepareShowOverlay(event)
140
- }
141
- }}>
142
- <ChevronRight />
143
- </IconBox>
144
- }
145
- </li>
193
+ </ReactTooltip>
194
+ </Link>
195
+ {/*shouldShowOverlay() &&
196
+ <IconBox size="sm" className="section-submenu-icon"
197
+ as="button"
198
+ aria-label={interpolate(t.menuOptions, label)}
199
+ aria-controls={MENU_OVERLAY_ID}
200
+ aria-expanded={false}
201
+ onKeyDown={(event) => {
202
+ if (event.key === 'Enter') {
203
+ prepareShowOverlay(event)
204
+ }
205
+ }}>
206
+ <ChevronRight />
207
+ </IconBox>
208
+ */}
146
209
  </>
147
210
  )
148
211
  }
149
212
 
213
+ /**
214
+ * Renders the overlay content.
215
+ * @param props the content of the overlay, can be either customized (react component), a config object or a function that creates the
216
+ * config object.
217
+ * @returns the content
218
+ */
150
219
  const OverlayRenderer = ({ content, customContent }: Pick<MenuSection, 'content' | 'customContent'>) => {
151
220
  if (customContent) {
152
- return <div id="custom-selectable-item"> {customContent} </div>
221
+ return <div id="custom-selectable-item">{customContent}</div>
153
222
  }
154
223
 
155
224
  const data = typeof content === 'function' ? content() : content
156
225
  return <MenuContent {...data} />
157
226
  }
158
227
 
228
+ /**
229
+ * Renders a menu-sections interface.
230
+ *
231
+ * Considering the Stackspot UI, this is the "menu sections", not the "menu content", i.e. it's the first menu from left to right, the
232
+ * one with the icons and section names: the main menu.
233
+ *
234
+ * Attention: each section in the menu is identified by its index. To avoid errors, the array passed to this component must never change.
235
+ * To control whether or not to show an item, use the property `hidden`, instead of adding/removing it to the array. Example of error:
236
+ * start the section menu with 5 items, remove the 5th item and add another item. The previous 5th item and the new 5th items are different,
237
+ * but since we identify the item by its index, the two different sections will be rendered as if they were the same, causing a react hook
238
+ * call order problem.
239
+ *
240
+ * @param props the props for the component {@link MenuProps}.
241
+ */
159
242
  export const MenuSections = ({ sections = [], ...props }: MenuProps) => {
160
243
  const Link = useAnchorTag()
244
+ const [showSupport, setShowSupport] = useState(true)
161
245
  const t = useTranslate(dictionary)
162
246
  // this is a mock state only used to force an update on the component.
163
247
  const [_, setUpdate] = useState(0)
164
248
 
165
- const toggleMenu = useCallback((hasContent: boolean) => {
249
+ const toggleMenu = useCallback(() => {
166
250
  const layout = document.getElementById('layout')
167
251
  if (!layout) return
168
252
  if (layout.classList.contains('menu-compact')) {
169
253
  layout.classList.remove('menu-compact')
254
+ setShowSupport(true)
170
255
  } else {
171
256
  layout.classList.add('menu-compact')
257
+ setShowSupport(false)
172
258
  }
173
259
 
174
- if (hasContent) {
175
- if (layout.classList.contains('menu-content-visible')) {
176
- layout.classList.remove('menu-content-visible')
177
- } else {
178
- layout.classList.add('menu-content-visible')
179
- }
180
- }
260
+ layout.classList.add('menu-manual')
261
+
181
262
  setUpdate(current => current + 1)
182
263
  }, [])
183
264
  // the current overlay showing, when the user hovers the section icon. This is the index of the item in the sections array.
184
265
  const [currentOverlay, setCurrentOverlay] = useState<number | undefined>()
185
266
 
267
+ //Custom items don't have overlay.
268
+ const sectionsWithoutCustom = useMemo(() => sections.filter((s) => !s.type), [sections]) as MenuSection[]
269
+ const sectionItemsOverlay = useMemo(
270
+ () => sectionsWithoutCustom.flatMap((s: MenuSection) => 'children' in s ? s.children?.map((c) => c) : s), [sectionsWithoutCustom],
271
+ )
272
+
186
273
  const sectionItems = useMemo(
187
- () => sections.filter(s => !s.hidden).map((s, i) => <Section key={i} id={i} {...s} setCurrentOverlay={setCurrentOverlay}
188
- hasContent={!!props.content || !!props.customContent} />),
189
- [sections],
274
+ () => sections.reduce<JSX.Element[]>(
275
+ (result, s, i) => {
276
+ if (s.label === t.contactUs && !showSupport) {
277
+ return result
278
+ }
279
+ if (s.type) {
280
+ return (s.hidden ? result : [
281
+ ...result, <Box key={`custom-element-${i}`}
282
+ className={s.className || ''}>
283
+ <Flex alignItems="center" justifyContent="center" flexWrap="nowrap" px={3} w="100%">
284
+ <IconBox className="custom-element-icon section-icon" >{s.icon}</IconBox>
285
+ </Flex>
286
+ <Box className="custom-element">
287
+ {s.children}
288
+ </Box>
289
+ </Box>,
290
+ ])
291
+ }
292
+ return (s.hidden ? result : [
293
+ ...result,
294
+ <li
295
+ role="menuitem"
296
+ key={s.label as string}
297
+ className={listToClass(['section-submenu', 'no-indentation', s.className || undefined, s.active ? 'active' : undefined])}>
298
+ {'children' in s
299
+ ? <CollapsibleSection listItems={sectionItemsOverlay} {...s} setCurrentOverlay={setCurrentOverlay} />
300
+ : <Section key={i} id={sectionItemsOverlay.findIndex((sio) => sio?.label === s.label)}
301
+ {...s} setCurrentOverlay={setCurrentOverlay} hasContent={!!props.content || !!props.customContent} />}
302
+ </li>,
303
+ ])
304
+
305
+ }, [],
306
+ ),
307
+ [sections, showSupport],
190
308
  )
191
309
 
192
310
  function onPressEscape() {
@@ -194,12 +312,14 @@ export const MenuSections = ({ sections = [], ...props }: MenuProps) => {
194
312
  hideOverlayImmediately()
195
313
  }
196
314
 
315
+ // @ts-ignore: unused (remove when validated by the design team)
197
316
  const { keyboardControlledElement: overlayRef, attachKeyboardListeners, detachKeyboardListeners } = useKeyboardControls({
198
317
  onPressEscape,
199
318
  querySelectors: 'li a.action, #custom-selectable-item button, #custom-selectable-item input',
200
319
  })
201
320
 
202
- // fixme: this should definitely not be handled like this...
321
+ // this only works because we have a single section menu in the site. This workaround was created since the keyboard controls were
322
+ // transformed into a hook. This is not ideal and it would be a good idea to rewrite this code without the need for this.
203
323
  attachKeyboardListenersForOverlay = attachKeyboardListeners
204
324
  detachKeyboardListenersForOverlay = detachKeyboardListeners
205
325
 
@@ -208,63 +328,192 @@ export const MenuSections = ({ sections = [], ...props }: MenuProps) => {
208
328
  Below, the key is of extreme importance. It ensures React will consider every section content to be an entirely different
209
329
  component. Without this, React would see the content changing every time a new section is hovered. Since the content might be a
210
330
  hook, this would cause some serious problems. */
331
+ // @ts-ignore: unused (remove when validated by the design team)
211
332
  function renderMenuOverlay() {
212
333
  if (currentOverlay === undefined) return null
213
- const shouldRenderMenuContentInstead = !isMenuContentVisible() && sections[currentOverlay].active &&
334
+ const shouldRenderMenuContentInstead = !isMenuContentVisible() && sectionItemsOverlay[currentOverlay]?.active &&
214
335
  (!!props.content || !!props.customContent)
215
336
  return shouldRenderMenuContentInstead
216
337
  ? <OverlayRenderer key={'contentKey' in props ? props.contentKey : undefined} content={props.content}
217
338
  customContent={props.customContent} />
218
- : <OverlayRenderer key={currentOverlay} content={sections[currentOverlay].content}
219
- customContent={sections[currentOverlay].customContent} />
339
+ : <OverlayRenderer key={currentOverlay} content={sectionItemsOverlay[currentOverlay]?.content}
340
+ customContent={sectionItemsOverlay[currentOverlay]?.customContent} />
220
341
  }
221
342
 
222
343
  return (
223
344
  <>
224
- <ul>{sectionItems}</ul>
225
-
226
- <Flex mb={7} alignItems="center">
227
- <button className="toggle sections-footer" onClick={() => toggleMenu(!!props.content || !!props.customContent)}
228
- title={t.toggle} tabIndex={-1}>
229
- <IconBox>
230
- <Expand className="expand" />
231
- <Collapse className="collapse" />
232
- </IconBox>
233
- <Text appearance="microtext1" ml={8} className="collapse" colorScheme="light.contrastText">{t.hide}</Text>
234
- </button>
345
+ <MenuSectionGroup className="open root no-indentation">{sectionItems}</MenuSectionGroup>
346
+
347
+ <Flex alignItems="center">
348
+ <RateAndContactUsItem {...props} showSupport={showSupport} />
349
+ <ReactTooltip
350
+ text={t.toggle}
351
+ position="right"
352
+ tabIndex={-1}
353
+ disableTooltip={() => !document.getElementById(elementIds.layout)}
354
+ target={document.getElementById(elementIds.layout)}
355
+ sx={{ width: 'inherit' }}
356
+ >
357
+ <button aria-label={t.toggle} role="menuitem" className="toggle sections-footer" onClick={toggleMenu}>
358
+ <IconBox>
359
+ <Expand className="expand" />
360
+ <Collapse className="collapse" />
361
+ </IconBox>
362
+ <Text appearance="microtext1" ml={8} className="collapse" colorScheme="light.contrastText">{t.hide}</Text>
363
+ </button>
364
+ </ReactTooltip>
235
365
  {(props.settings?.show) &&
236
- <Link href={props.settings?.href} onClick={props.settings?.onClick}
366
+ <Link
367
+ href={props.settings?.href}
368
+ onClick={props.settings?.onClick}
237
369
  className="sections-footer toggle"
238
- {...(props.settings.active ? { 'aria-current': 'page' } : undefined)}>
239
- <IconBox aria-label={t.settingsIcon}>
240
- <Cog />
241
- </IconBox>
242
- <Text appearance="microtext1" ml={8} className="collapse">{t.settings}</Text>
370
+ aria-label={t.settings}
371
+ {...(props.settings.active ? { 'aria-current': 'page' } : undefined)}
372
+ >
373
+ <ReactTooltip
374
+ text={t.settings}
375
+ position="right"
376
+ tabIndex={-1}
377
+ disableTooltip={() => !document.getElementById(elementIds.layout)}
378
+ target={document.getElementById(elementIds.layout)}
379
+ sx={{ width: 'inherit' }}
380
+ >
381
+ <IconBox aria-label={t.settingsIcon}>
382
+ <Cog />
383
+ </IconBox>
384
+ <Text appearance="microtext1" ml={8} className="collapse">{t.settings}</Text>
385
+ </ReactTooltip>
243
386
  </Link>
244
387
  }
245
388
  </Flex>
246
389
 
247
- <div id={MENU_OVERLAY_ID} onMouseEnter={showOverlay} onMouseLeave={hideOverlay} ref={overlayRef}>
390
+ {/* <div id={MENU_OVERLAY_ID} onMouseEnter={showOverlay} onMouseLeave={hideOverlay} ref={overlayRef}>
248
391
  {renderMenuOverlay()}
249
- <div className="arrow"></div>
250
- </div>
392
+ </div> */}
393
+ </>
394
+ )
395
+ }
396
+
397
+ const RateAndContactUsItem = ({ showSupport, ...props }: Omit<MenuProps, 'sections'> & { showSupport?: boolean }) => {
398
+ const t = useTranslate(dictionary)
399
+ const alreadyAnswered = localStorage.getItem('RATED_US_IN')
400
+ const hasAnsweredLess30Days = alreadyAnswered ? isLessThan30Days(new Date(+alreadyAnswered), new Date(Date.now())) : false
401
+ const Link = useAnchorTag()
402
+
403
+ //This effect is only used to remove from local storage unused keys from
404
+ //previous rate us and contact us versions. We do not want to fill the users storage
405
+ //with things that are not used anymore.
406
+ useEffect(() => {
407
+ localStorage.removeItem('CONTACT_POPOVER')
408
+ localStorage.removeItem('RATE_POPOVER')
409
+ localStorage.removeItem('viewNpsItem')
410
+ }, [])
411
+
412
+ return <>
413
+ {(props.rateUs?.show && !hasAnsweredLess30Days) &&
414
+ <button role="menuitem" className="toggle sections-footer" onClick={props.rateUs?.onClick}
415
+ {...(props.rateUs.active ? { 'aria-current': 'page' } : undefined)}>
416
+ <IconBox aria-label={t.rateUsIcon} sx={{ paddingTop: '2px' }}>
417
+ <StarFillWithGradient id="svg-icon" className="gradient-svg rotate-icon" />
418
+ </IconBox>
419
+ <Text appearance="microtext1" ml={8} sx={{ marginTop: '3px' }}
420
+ className="collapse gradient grow-shrink" colorScheme="light.contrastText">{t.rateUs}</Text>
421
+ </button>
422
+ }
423
+ {(props.contactUs?.show && showSupport) && (
424
+ <Link href={props.contactUs?.href} className="toggle sections-footer" onClick={props.contactUs?.onClick}
425
+ {...(props.contactUs.active ? { 'aria-current': 'page' } : undefined)} target={props.contactUs?.target}>
426
+ <IconBox aria-label={t.contactIcon}>
427
+ <Support />
428
+ </IconBox>
429
+ <Text appearance="microtext1" ml={8} sx={{ marginTop: '3px' }}
430
+ className="collapse" colorScheme="light.contrastText">{t.contactUs}</Text>
431
+ </Link>
432
+ )}
433
+ </>
434
+ }
435
+
436
+ /**
437
+ * A menu item that is actually a subgroup and can be collapsed/expanded.
438
+ * @param props the props for the component {@link MenuSectionGroup}.
439
+ */
440
+ const CollapsibleSection = (
441
+ { label, open: initialValue, children, icon, setCurrentOverlay, listItems, replaceWithChildrenWhenCollapsed = true }: (
442
+ MenuSection & { setCurrentOverlay: (id: number | undefined) => void, listItems: (MenuSection | undefined)[] }
443
+ )) => {
444
+ const [open, setOpen] = useState(initialValue || false)
445
+ const menuGroupId = `menuGroup${label}`
446
+ const items = useMemo(() => children?.filter(i => !i.hidden).map(
447
+ (c) => (
448
+ <li
449
+ role="menuitem"
450
+ key={c.label as string}
451
+ title={c.label as string}
452
+ className={`section-submenu ${c.className} ${c.active ? 'active' : ''}`}>
453
+ <Section
454
+ id={listItems?.findIndex((item) => item?.label === c.label)} key={c.label as string}
455
+ {...c} hasContent={false} setCurrentOverlay={setCurrentOverlay} />
456
+ </li>
457
+ )), [children])
458
+
459
+ return (
460
+ <>
461
+ <a
462
+ onClick={() => setOpen(!open)}
463
+ onKeyDown={e => e.key === 'Enter' && setOpen(!open)}
464
+ className={listToClass(['item-row', replaceWithChildrenWhenCollapsed && 'hidden-when-collapsed'])}
465
+ tabIndex={0}
466
+ aria-controls={menuGroupId}
467
+ aria-expanded={open}
468
+ >
469
+ <ReactTooltip
470
+ text={label as string}
471
+ position="right"
472
+ tabIndex={-1}
473
+ target={document.getElementById(elementIds.layout)}
474
+ sx={{ width: 'inherit' }}
475
+ >
476
+ <Flex alignItems="center" px={3} style={{ width: '100%' }}>
477
+ <IconBox className="section-icon">{icon}</IconBox>
478
+ <Text appearance="microtext1" className="section-label" ml={3} colorScheme="light.contrastText">
479
+ {label as string}
480
+ </Text>
481
+ <IconBox style={{ marginLeft: 'auto' }} className="chevron-icon-box">
482
+ <ChevronUpFill className={listToClass(['chevron', open ? 'open' : ''])} />
483
+ </IconBox>
484
+ </Flex>
485
+ </ReactTooltip>
486
+ </a>
487
+ <MenuSectionGroup id={menuGroupId}
488
+ className={`${open ? 'open' : ''} root no-indentation`}
489
+ aria-hidden={!open}>
490
+ {items}
491
+ </MenuSectionGroup>
251
492
  </>
252
493
  )
253
494
  }
254
495
 
255
496
  const dictionary = {
256
497
  en: {
257
- toggle: 'Show or hide the menu',
498
+ toggle: 'Collapse or expand the menu',
258
499
  menuOptions: 'View $0 menu options',
259
500
  settings: 'Settings',
260
501
  settingsIcon: 'Settings icon',
261
502
  hide: 'Hide',
503
+ contactUs: 'Support',
504
+ contactIcon: 'Contact icon',
505
+ rateUs: 'Rate us',
506
+ rateUsIcon: 'Rate us icon',
262
507
  },
263
508
  pt: {
264
- toggle: 'Visualizar ou esconder o menu',
509
+ toggle: 'Contrair ou expandir o menu',
265
510
  menuOptions: 'Visualizar opções do menu $0',
266
511
  settings: 'Configurações',
267
- settingsIcon: 'Icone de configurações',
512
+ settingsIcon: 'Ícone de configurações',
268
513
  hide: 'Esconder',
514
+ contactUs: 'Suporte',
515
+ contactIcon: 'Ícone de contato',
516
+ rateUs: 'Avalie-nos',
517
+ rateUsIcon: 'Avalie-nos icon',
269
518
  },
270
519
  } satisfies Dictionary
@@ -1,15 +1,15 @@
1
1
  import { IconBox, Text } from '@citric/core'
2
2
  import { ArrowRight, Select } from '@citric/icons'
3
3
  import { LoadingCircular } from '@citric/ui'
4
+ import { useCheckTextOverflow } from '@stack-spot/portal-components'
5
+ import { ListAction, SelectionList } from '@stack-spot/portal-components/SelectionList'
6
+ import { useAnchorTag } from '@stack-spot/portal-components/anchor'
4
7
  import { theme } from '@stack-spot/portal-theme'
5
8
  import { Dictionary, interpolate, useTranslate } from '@stack-spot/portal-translate'
6
9
  import { KeyboardEvent, useMemo, useRef, useState } from 'react'
7
10
  import { styled } from 'styled-components'
8
- import { useAnchorTag } from '../../layout-context'
9
- import { ListAction, SelectionList } from '../SelectionList'
10
11
  import { MENU_CONTENT_PADDING as PADDING } from './constants'
11
12
  import { Selector } from './types'
12
- import { useCheckTextOverflow } from './use-check-text-overflow'
13
13
 
14
14
  const SelectorBox = styled.div`
15
15
  position: relative;
@@ -81,6 +81,11 @@ const SelectorBox = styled.div`
81
81
  }
82
82
  `
83
83
 
84
+ /**
85
+ * A selector component to render inside a menu-content. Allows the user to select another page.
86
+ *
87
+ * @param props the React props for the component {@link Selector}.
88
+ */
84
89
  export const PageSelector = ({ options, value, button, loading, title }: Selector) => {
85
90
  const Link = useAnchorTag()
86
91
  const t = useTranslate(dictionary)
@@ -113,7 +118,13 @@ export const PageSelector = ({ options, value, button, loading, title }: Selecto
113
118
  ? <LoadingCircular />
114
119
  : (
115
120
  <>
116
- {title && <Text colorScheme="light.700" sx={{ mb: 3 }}>{title}</Text>}
121
+ {title && <Text
122
+ colorScheme="light.700"
123
+ sx={{ mb: 3 }}
124
+ id="pageSelectorTitle"
125
+ aria-label={title}>
126
+ {title}
127
+ </Text>}
117
128
  <button
118
129
  onClick={() => setVisible(true)}
119
130
  onKeyDown={(e: KeyboardEvent<HTMLButtonElement>) => e.key === 'Enter' && setVisible(true)}
@@ -123,6 +134,7 @@ export const PageSelector = ({ options, value, button, loading, title }: Selecto
123
134
  aria-expanded={visible}
124
135
  aria-controls={id.current}
125
136
  aria-haspopup="listbox"
137
+ aria-labelledby="pageSelectorTitle"
126
138
  >
127
139
  {selected?.icon && <IconBox>{selected?.icon}</IconBox>}
128
140
  {isTextLabel ?