@cyberskill/design 1.1.0 → 1.2.0

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 (358) hide show
  1. package/DESIGN.md +25 -3
  2. package/README.md +7 -7
  3. package/SKILL.md +4 -4
  4. package/VERSION +1 -1
  5. package/_ds_bundle.js +819 -416
  6. package/_ds_manifest.json +1 -1
  7. package/_esm/cs.mjs +18 -4
  8. package/_esm/react.d.ts +2 -0
  9. package/_esm/react.mjs +1 -0
  10. package/base/a11y.css +18 -0
  11. package/base/ai.css +2 -2
  12. package/base/collections.css +8 -3
  13. package/base/components.css +11 -2
  14. package/base/controls.css +61 -48
  15. package/base/data.css +3 -3
  16. package/base/feedback.css +5 -0
  17. package/base/forms.css +9 -3
  18. package/base/glass.css +6 -0
  19. package/base/navigation.css +8 -2
  20. package/base/reset.css +1 -1
  21. package/base/responsive.css +25 -0
  22. package/base/shell.css +7 -1
  23. package/components/_i18n/i18n.js +13 -1
  24. package/components/_i18n/strings.js +52 -3
  25. package/components/ai/ConfidenceMeter.jsx +8 -6
  26. package/components/data/Watermark.jsx +11 -3
  27. package/components/datatable/DataGrid.jsx +10 -1
  28. package/components/dialog/Dialog.jsx +13 -45
  29. package/components/feedback/ProgressBar.jsx +4 -2
  30. package/components/forms/Cascader.jsx +69 -6
  31. package/components/forms/Editor.jsx +37 -2
  32. package/components/forms/FileUpload.jsx +22 -8
  33. package/components/forms/Switch.jsx +16 -2
  34. package/components/forms/TimePicker.jsx +3 -1
  35. package/components/icon/Icon.jsx +1 -0
  36. package/components/navigation/CommandPalette.jsx +22 -7
  37. package/components/navigation/Toolbar.jsx +2 -1
  38. package/components/overlays/AlertDialog.jsx +19 -49
  39. package/components/overlays/Drawer.jsx +24 -20
  40. package/components/overlays/OverlayManager.d.ts +40 -0
  41. package/components/overlays/OverlayManager.jsx +214 -0
  42. package/components/overlays/OverlayManager.prompt.md +12 -0
  43. package/components/overlays/Tour.jsx +14 -7
  44. package/dist/styles.min.css +2004 -0
  45. package/docs/benchmark-rubric.md +1 -1
  46. package/docs/ci-cd.md +3 -1
  47. package/docs/consuming.md +6 -6
  48. package/docs/contrast-report.md +1 -1
  49. package/docs/conventions.md +4 -4
  50. package/docs/decisions/container-query-migration.md +18 -0
  51. package/docs/decisions/overlay-manager-design.md +101 -0
  52. package/docs/decisions.md +5 -5
  53. package/docs/design-styles.md +1 -1
  54. package/docs/plans/cyberskill-design-system-audit-and-12-month-evolution-plan.md +1490 -0
  55. package/docs/quality-gates.md +21 -9
  56. package/docs/release-notes.md +10 -2
  57. package/docs/status/data/status-feed.json +1 -1
  58. package/docs/status/data/task/TASK-REL-002.js +1 -0
  59. package/docs/status/index.html +7 -6
  60. package/docs/status/status-legacy.html +13 -12
  61. package/docs/sync.md +1 -1
  62. package/docs/tasks/BACKLOG.md +1 -0
  63. package/docs/tasks/_state/commit-links.yaml +14 -0
  64. package/docs/tasks/release/TASK-REL-002-republish-1-1-1/spec.md +54 -0
  65. package/docs/vi/benchmark-rubric.md +1 -1
  66. package/docs/vi/ci-cd.md +3 -1
  67. package/docs/vi/consuming.md +6 -6
  68. package/docs/vi/contrast-report.md +1 -1
  69. package/docs/vi/conventions.md +2 -2
  70. package/docs/vi/decisions.md +5 -5
  71. package/docs/vi/quality-gates.md +21 -9
  72. package/docs/vi/release-notes.md +10 -2
  73. package/docs/vi/sync.md +1 -1
  74. package/guidelines/atomic-view.html +11 -7
  75. package/llms.txt +5 -5
  76. package/package.json +14 -6
  77. package/templates/_ds-base.canonical.js +55 -0
  78. package/templates/ai-cluster-demo.html +2 -2
  79. package/templates/app-shell/AppShell.dc.html +5 -3
  80. package/templates/app-shell/ds-base.js +33 -1
  81. package/templates/app-shell/support.js +13 -20
  82. package/templates/article/Article.dc.html +5 -1
  83. package/templates/article/ds-base.js +33 -1
  84. package/templates/article/support.js +13 -20
  85. package/templates/auth/Auth.dc.html +10 -5
  86. package/templates/auth/content-schema.json +12 -0
  87. package/templates/auth/ds-base.js +33 -1
  88. package/templates/auth/support.js +13 -20
  89. package/templates/bod-deck/BodDeck.dc.html +11 -10
  90. package/templates/bod-deck/ds-base.js +33 -1
  91. package/templates/bod-deck/support.js +13 -20
  92. package/templates/bod-investor-update/BodInvestorUpdate.dc.html +4 -3
  93. package/templates/bod-investor-update/ds-base.js +33 -1
  94. package/templates/bod-investor-update/support.js +13 -20
  95. package/templates/bod-memo/BodMemo.dc.html +6 -5
  96. package/templates/bod-memo/ds-base.js +33 -1
  97. package/templates/bod-memo/support.js +13 -20
  98. package/templates/bod-minutes/BodMinutes.dc.html +2 -1
  99. package/templates/bod-minutes/ds-base.js +33 -1
  100. package/templates/bod-minutes/support.js +13 -20
  101. package/templates/bod-report/BodReport.dc.html +3 -2
  102. package/templates/bod-report/ds-base.js +33 -1
  103. package/templates/bod-report/support.js +13 -20
  104. package/templates/bod-resolution/BodResolution.dc.html +5 -4
  105. package/templates/bod-resolution/ds-base.js +33 -1
  106. package/templates/bod-resolution/support.js +13 -20
  107. package/templates/culture-event-invite/CultureEventInvite.dc.html +3 -2
  108. package/templates/culture-event-invite/ds-base.js +33 -1
  109. package/templates/culture-event-invite/support.js +13 -20
  110. package/templates/dashboard/Dashboard.dc.html +7 -3
  111. package/templates/dashboard/ds-base.js +33 -1
  112. package/templates/dashboard/support.js +13 -20
  113. package/templates/delivery-change-note/DeliveryChangeNote.dc.html +2 -1
  114. package/templates/delivery-change-note/ds-base.js +33 -1
  115. package/templates/delivery-change-note/support.js +13 -20
  116. package/templates/delivery-kickoff/DeliveryKickoff.dc.html +3 -2
  117. package/templates/delivery-kickoff/ds-base.js +33 -1
  118. package/templates/delivery-kickoff/support.js +13 -20
  119. package/templates/delivery-qbr-deck/DeliveryQbrDeck.dc.html +6 -5
  120. package/templates/delivery-qbr-deck/ds-base.js +33 -1
  121. package/templates/delivery-qbr-deck/support.js +13 -20
  122. package/templates/delivery-runbook/DeliveryRunbook.dc.html +3 -2
  123. package/templates/delivery-runbook/ds-base.js +33 -1
  124. package/templates/delivery-runbook/support.js +13 -20
  125. package/templates/delivery-status-email/DeliveryStatusEmail.dc.html +3 -2
  126. package/templates/delivery-status-email/ds-base.js +33 -1
  127. package/templates/delivery-status-email/support.js +13 -20
  128. package/templates/doc-style-guide/DocStyleGuide.dc.html +67 -64
  129. package/templates/doc-style-guide/ds-base.js +33 -1
  130. package/templates/doc-style-guide/support.js +13 -20
  131. package/templates/doc-suite-index/DocSuiteIndex.dc.html +46 -45
  132. package/templates/doc-suite-index/ds-base.js +33 -1
  133. package/templates/doc-suite-index/support.js +13 -20
  134. package/templates/doc-templates/DocTemplates.dc.html +42 -41
  135. package/templates/doc-templates/ds-base.js +33 -1
  136. package/templates/doc-templates/support.js +13 -20
  137. package/templates/email/Email.dc.html +3 -2
  138. package/templates/email/ds-base.js +33 -1
  139. package/templates/email/support.js +13 -20
  140. package/templates/finance-dunning-email/FinanceDunningEmail.dc.html +4 -3
  141. package/templates/finance-dunning-email/ds-base.js +33 -1
  142. package/templates/finance-dunning-email/support.js +13 -20
  143. package/templates/finance-expense-report/FinanceExpenseReport.dc.html +4 -3
  144. package/templates/finance-expense-report/ds-base.js +33 -1
  145. package/templates/finance-expense-report/support.js +13 -20
  146. package/templates/finance-invoice/FinanceInvoice.dc.html +4 -3
  147. package/templates/finance-invoice/ds-base.js +33 -1
  148. package/templates/finance-invoice/support.js +13 -20
  149. package/templates/finance-quote/FinanceQuote.dc.html +4 -3
  150. package/templates/finance-quote/ds-base.js +33 -1
  151. package/templates/finance-quote/support.js +13 -20
  152. package/templates/hr-announcement/HrAnnouncement.dc.html +5 -4
  153. package/templates/hr-announcement/ds-base.js +33 -1
  154. package/templates/hr-announcement/support.js +13 -20
  155. package/templates/hr-interview-kit/HrInterviewKit.dc.html +4 -3
  156. package/templates/hr-interview-kit/ds-base.js +33 -1
  157. package/templates/hr-interview-kit/support.js +13 -20
  158. package/templates/hr-offboarding/HrOffboarding.dc.html +2 -1
  159. package/templates/hr-offboarding/ds-base.js +33 -1
  160. package/templates/hr-offboarding/support.js +13 -20
  161. package/templates/hr-performance-review/HrPerformanceReview.dc.html +2 -1
  162. package/templates/hr-performance-review/ds-base.js +33 -1
  163. package/templates/hr-performance-review/support.js +13 -20
  164. package/templates/hr-pip/HrPip.dc.html +2 -1
  165. package/templates/hr-pip/ds-base.js +33 -1
  166. package/templates/hr-pip/support.js +13 -20
  167. package/templates/kitchen-sink.html +9 -4
  168. package/templates/legal-msa/LegalMsa.dc.html +2 -1
  169. package/templates/legal-msa/ds-base.js +33 -1
  170. package/templates/legal-msa/support.js +13 -20
  171. package/templates/legal-sow/LegalSow.dc.html +2 -1
  172. package/templates/legal-sow/ds-base.js +33 -1
  173. package/templates/legal-sow/support.js +13 -20
  174. package/templates/marketing-campaign-brief/MarketingCampaignBrief.dc.html +3 -2
  175. package/templates/marketing-campaign-brief/ds-base.js +33 -1
  176. package/templates/marketing-campaign-brief/support.js +13 -20
  177. package/templates/marketing-case-study/MarketingCaseStudy.dc.html +5 -4
  178. package/templates/marketing-case-study/ds-base.js +33 -1
  179. package/templates/marketing-case-study/support.js +13 -20
  180. package/templates/marketing-launch/MarketingLaunch.dc.html +6 -5
  181. package/templates/marketing-launch/ds-base.js +33 -1
  182. package/templates/marketing-launch/support.js +13 -20
  183. package/templates/marketing-newsletter/MarketingNewsletter.dc.html +13 -12
  184. package/templates/marketing-newsletter/ds-base.js +33 -1
  185. package/templates/marketing-newsletter/support.js +13 -20
  186. package/templates/marketing-page/MarketingPage.dc.html +17 -12
  187. package/templates/marketing-page/ds-base.js +33 -1
  188. package/templates/marketing-page/support.js +13 -20
  189. package/templates/marketing-press-release/MarketingPressRelease.dc.html +2 -1
  190. package/templates/marketing-press-release/ds-base.js +33 -1
  191. package/templates/marketing-press-release/support.js +13 -20
  192. package/templates/marketing-social-kit/MarketingSocialKit.dc.html +5 -4
  193. package/templates/marketing-social-kit/ds-base.js +33 -1
  194. package/templates/marketing-social-kit/support.js +13 -20
  195. package/templates/sales-one-pager/SalesOnePager.dc.html +5 -4
  196. package/templates/sales-one-pager/ds-base.js +33 -1
  197. package/templates/sales-one-pager/support.js +13 -20
  198. package/templates/sales-proposal-deck/SalesProposalDeck.dc.html +7 -6
  199. package/templates/sales-proposal-deck/ds-base.js +33 -1
  200. package/templates/sales-proposal-deck/support.js +13 -20
  201. package/templates/settings/Settings.dc.html +5 -1
  202. package/templates/settings/ds-base.js +33 -1
  203. package/templates/settings/support.js +13 -20
  204. package/templates/slide-deck/SlideDeck.dc.html +5 -4
  205. package/templates/slide-deck/ds-base.js +33 -1
  206. package/templates/slide-deck/support.js +13 -20
  207. package/templates/team-meeting-agenda/TeamMeetingAgenda.dc.html +2 -1
  208. package/templates/team-meeting-agenda/ds-base.js +33 -1
  209. package/templates/team-meeting-agenda/support.js +13 -20
  210. package/templates/tech-incident-report/TechIncidentReport.dc.html +3 -2
  211. package/templates/tech-incident-report/ds-base.js +33 -1
  212. package/templates/tech-incident-report/support.js +13 -20
  213. package/templates/tech-release-notes/TechReleaseNotes.dc.html +6 -5
  214. package/templates/tech-release-notes/ds-base.js +33 -1
  215. package/templates/tech-release-notes/support.js +13 -20
  216. package/templates/tech-retro/TechRetro.dc.html +4 -3
  217. package/templates/tech-retro/ds-base.js +33 -1
  218. package/templates/tech-retro/support.js +13 -20
  219. package/templates/tech-rfc/TechRfc.dc.html +3 -2
  220. package/templates/tech-rfc/ds-base.js +33 -1
  221. package/templates/tech-rfc/support.js +13 -20
  222. package/templates/vn-appointment-transfer/VnAppointmentTransfer.dc.html +36 -35
  223. package/templates/vn-appointment-transfer/ds-base.js +33 -1
  224. package/templates/vn-appointment-transfer/support.js +13 -20
  225. package/templates/vn-certificate-of-employment/VnCertificateOfEmployment.dc.html +27 -26
  226. package/templates/vn-certificate-of-employment/ds-base.js +33 -1
  227. package/templates/vn-certificate-of-employment/support.js +13 -20
  228. package/templates/vn-code-of-conduct/VnCodeOfConduct.dc.html +44 -43
  229. package/templates/vn-code-of-conduct/ds-base.js +33 -1
  230. package/templates/vn-code-of-conduct/support.js +13 -20
  231. package/templates/vn-compensation-regulation/VnCompensationRegulation.dc.html +53 -52
  232. package/templates/vn-compensation-regulation/ds-base.js +33 -1
  233. package/templates/vn-compensation-regulation/support.js +13 -20
  234. package/templates/vn-contract-amendment/VnContractAmendment.dc.html +41 -40
  235. package/templates/vn-contract-amendment/ds-base.js +33 -1
  236. package/templates/vn-contract-amendment/support.js +13 -20
  237. package/templates/vn-contractor-agreement/VnContractorAgreement.dc.html +50 -49
  238. package/templates/vn-contractor-agreement/ds-base.js +33 -1
  239. package/templates/vn-contractor-agreement/support.js +13 -20
  240. package/templates/vn-data-protection-policy/VnDataProtectionPolicy.dc.html +54 -53
  241. package/templates/vn-data-protection-policy/ds-base.js +33 -1
  242. package/templates/vn-data-protection-policy/support.js +13 -20
  243. package/templates/vn-disciplinary-case-file/VnDisciplinaryCaseFile.dc.html +72 -71
  244. package/templates/vn-disciplinary-case-file/ds-base.js +33 -1
  245. package/templates/vn-disciplinary-case-file/support.js +13 -20
  246. package/templates/vn-disciplinary-schedule/VnDisciplinarySchedule.dc.html +21 -20
  247. package/templates/vn-disciplinary-schedule/ds-base.js +33 -1
  248. package/templates/vn-disciplinary-schedule/support.js +13 -20
  249. package/templates/vn-employee-request/VnEmployeeRequest.dc.html +54 -53
  250. package/templates/vn-employee-request/ds-base.js +33 -1
  251. package/templates/vn-employee-request/support.js +13 -20
  252. package/templates/vn-exit-handover/VnExitHandover.dc.html +122 -121
  253. package/templates/vn-exit-handover/ds-base.js +33 -1
  254. package/templates/vn-exit-handover/support.js +13 -20
  255. package/templates/vn-foreign-employee-pack/VnForeignEmployeePack.dc.html +31 -30
  256. package/templates/vn-foreign-employee-pack/ds-base.js +33 -1
  257. package/templates/vn-foreign-employee-pack/support.js +13 -20
  258. package/templates/vn-grassroots-democracy/VnGrassrootsDemocracy.dc.html +54 -53
  259. package/templates/vn-grassroots-democracy/ds-base.js +33 -1
  260. package/templates/vn-grassroots-democracy/support.js +13 -20
  261. package/templates/vn-infosec-policy/VnInfosecPolicy.dc.html +50 -49
  262. package/templates/vn-infosec-policy/ds-base.js +33 -1
  263. package/templates/vn-infosec-policy/support.js +13 -20
  264. package/templates/vn-internal-labor-regulations/VnInternalLaborRegulations.dc.html +84 -83
  265. package/templates/vn-internal-labor-regulations/ds-base.js +33 -1
  266. package/templates/vn-internal-labor-regulations/support.js +13 -20
  267. package/templates/vn-internship-agreement/VnInternshipAgreement.dc.html +44 -43
  268. package/templates/vn-internship-agreement/ds-base.js +33 -1
  269. package/templates/vn-internship-agreement/support.js +13 -20
  270. package/templates/vn-job-description/VnJobDescription.dc.html +28 -27
  271. package/templates/vn-job-description/ds-base.js +33 -1
  272. package/templates/vn-job-description/support.js +13 -20
  273. package/templates/vn-labor-contract/VnLaborContract.dc.html +109 -108
  274. package/templates/vn-labor-contract/ds-base.js +33 -1
  275. package/templates/vn-labor-contract/support.js +13 -20
  276. package/templates/vn-labour-management-book/VnLabourManagementBook.dc.html +32 -31
  277. package/templates/vn-labour-management-book/ds-base.js +33 -1
  278. package/templates/vn-labour-management-book/support.js +13 -20
  279. package/templates/vn-mutual-nda/VnMutualNda.dc.html +45 -44
  280. package/templates/vn-mutual-nda/ds-base.js +33 -1
  281. package/templates/vn-mutual-nda/support.js +13 -20
  282. package/templates/vn-ndnca-ip/VnNdncaIp.dc.html +73 -72
  283. package/templates/vn-ndnca-ip/ds-base.js +33 -1
  284. package/templates/vn-ndnca-ip/support.js +13 -20
  285. package/templates/vn-offer-letter/VnOfferLetter.dc.html +32 -31
  286. package/templates/vn-offer-letter/ds-base.js +33 -1
  287. package/templates/vn-offer-letter/support.js +13 -20
  288. package/templates/vn-onboarding-checklist/VnOnboardingChecklist.dc.html +16 -15
  289. package/templates/vn-onboarding-checklist/ds-base.js +33 -1
  290. package/templates/vn-onboarding-checklist/support.js +13 -20
  291. package/templates/vn-pdpl-consent/VnPdplConsent.dc.html +43 -42
  292. package/templates/vn-pdpl-consent/ds-base.js +33 -1
  293. package/templates/vn-pdpl-consent/support.js +13 -20
  294. package/templates/vn-performance-evaluation-regulation/VnPerformanceEvaluationRegulation.dc.html +65 -64
  295. package/templates/vn-performance-evaluation-regulation/ds-base.js +33 -1
  296. package/templates/vn-performance-evaluation-regulation/support.js +13 -20
  297. package/templates/vn-performance-review-form/VnPerformanceReviewForm.dc.html +49 -48
  298. package/templates/vn-performance-review-form/ds-base.js +33 -1
  299. package/templates/vn-performance-review-form/support.js +13 -20
  300. package/templates/vn-phantom-stock/VnPhantomStock.dc.html +41 -40
  301. package/templates/vn-phantom-stock/ds-base.js +33 -1
  302. package/templates/vn-phantom-stock/support.js +13 -20
  303. package/templates/vn-probation-contract/VnProbationContract.dc.html +56 -55
  304. package/templates/vn-probation-contract/ds-base.js +33 -1
  305. package/templates/vn-probation-contract/support.js +13 -20
  306. package/templates/vn-receipt-acknowledgement/VnReceiptAcknowledgement.dc.html +28 -27
  307. package/templates/vn-receipt-acknowledgement/ds-base.js +33 -1
  308. package/templates/vn-receipt-acknowledgement/support.js +13 -20
  309. package/templates/vn-remote-work-policy/VnRemoteWorkPolicy.dc.html +44 -43
  310. package/templates/vn-remote-work-policy/ds-base.js +33 -1
  311. package/templates/vn-remote-work-policy/support.js +13 -20
  312. package/templates/vn-resignation-letter/VnResignationLetter.dc.html +20 -19
  313. package/templates/vn-resignation-letter/ds-base.js +33 -1
  314. package/templates/vn-resignation-letter/support.js +13 -20
  315. package/templates/vn-salary-scale/VnSalaryScale.dc.html +39 -38
  316. package/templates/vn-salary-scale/ds-base.js +33 -1
  317. package/templates/vn-salary-scale/support.js +13 -20
  318. package/templates/vn-sow-appendix/VnSowAppendix.dc.html +21 -20
  319. package/templates/vn-sow-appendix/ds-base.js +33 -1
  320. package/templates/vn-sow-appendix/support.js +13 -20
  321. package/templates/vn-termination-decision/VnTerminationDecision.dc.html +25 -24
  322. package/templates/vn-termination-decision/ds-base.js +33 -1
  323. package/templates/vn-termination-decision/support.js +13 -20
  324. package/templates/vn-total-rewards-appendix/VnTotalRewardsAppendix.dc.html +63 -62
  325. package/templates/vn-total-rewards-appendix/ds-base.js +33 -1
  326. package/templates/vn-total-rewards-appendix/support.js +13 -20
  327. package/templates/vn-training-commitment/VnTrainingCommitment.dc.html +39 -38
  328. package/templates/vn-training-commitment/ds-base.js +33 -1
  329. package/templates/vn-training-commitment/support.js +13 -20
  330. package/templates/vn-travel-expense-policy/VnTravelExpensePolicy.dc.html +50 -49
  331. package/templates/vn-travel-expense-policy/ds-base.js +33 -1
  332. package/templates/vn-travel-expense-policy/support.js +13 -20
  333. package/tokens/colors.css +19 -1
  334. package/tokens/component-tokens.css +1 -0
  335. package/tokens/elements.css +35 -2
  336. package/tokens/elevation.css +8 -2
  337. package/tokens/native/CSTokens.kt +35 -1
  338. package/tokens/native/CSTokens.swift +35 -1
  339. package/tokens/native/cs_tokens.dart +35 -1
  340. package/tokens/provenance.json +12 -12
  341. package/tokens/spacing.css +2 -0
  342. package/tokens/tokens.dtcg.json +111 -3
  343. package/tokens/tokens.js +46 -6
  344. package/tokens/tokens.json +45 -5
  345. package/tokens/typography.css +2 -0
  346. package/ui_kits/deck/index.html +4 -0
  347. package/ui_kits/status-hub/StatusHub.jsx +30 -4
  348. package/ui_kits/status-hub/index.html +3 -3
  349. package/ui_kits/status-hub/login.html +4 -0
  350. package/ui_kits/status-hub/project.html +4 -0
  351. package/ui_kits/status-hub/settings.html +35 -13
  352. package/ui_kits/status-hub/status.css +5 -1
  353. package/ui_kits/website/Website.jsx +25 -3
  354. package/ui_kits/website/careers.html +4 -0
  355. package/ui_kits/website/chat.html +5 -3
  356. package/ui_kits/website/index.html +3 -3
  357. package/ui_kits/website/site.css +2 -2
  358. package/ui_kits/website/work.html +4 -0
@@ -9,7 +9,8 @@ export function Toolbar({ items = [], overflowAfter, label, lang, className }) {
9
9
  const [ref, L] = useLang(lang);
10
10
  const t = makeT("Toolbar", L);
11
11
  const cut = overflowAfter != null ? overflowAfter : items.length;
12
- const head = items.slice(0, cut), tail = items.filter((x) => x !== "-").slice(cut);
12
+ const head = items.slice(0, cut);
13
+ const tail = items.slice(cut).filter((x) => x !== "-");
13
14
  React.useEffect(() => {
14
15
  if (!open) return;
15
16
  const d = (e) => { if (wrap.current && !wrap.current.contains(e.target)) setOpen(false); };
@@ -1,26 +1,12 @@
1
1
  import React from "react";
2
2
  import { makeT, useLang } from "../_i18n/i18n.js";
3
3
  import { cx } from "../_utils/cx.js";
4
-
5
- const FOCUSABLE = [
6
- 'a[href]',
7
- 'area[href]',
8
- 'button:not([disabled])',
9
- 'input:not([disabled]):not([type="hidden"])',
10
- 'select:not([disabled])',
11
- 'textarea:not([disabled])',
12
- 'audio[controls]',
13
- 'video[controls]',
14
- 'summary',
15
- 'iframe',
16
- '[contenteditable]:not([contenteditable="false"])',
17
- '[tabindex]:not([tabindex="-1"])',
18
- ].join(",");
4
+ import { useOverlayLayer } from "./OverlayManager.jsx";
19
5
 
20
6
  /**
21
7
  * CyberSkill AlertDialog — first-class destructive/confirm modal (role=alertdialog).
22
8
  * Distinct from Popconfirm (inline bubble) and Dialog (general modal). Focus trap +
23
- * restore; bilingual confirm/cancel via the registry.
9
+ * restore via overlay manager; bilingual confirm/cancel via the registry.
24
10
  */
25
11
  export function AlertDialog({
26
12
  open,
@@ -53,40 +39,24 @@ export function AlertDialog({
53
39
  onOpenChange?.(next);
54
40
  };
55
41
  const destructive = (tone ?? variant) === "destructive";
56
- const confirmRef = React.useRef(onConfirm); confirmRef.current = onConfirm;
57
- const cancelRef = React.useRef(onCancel); cancelRef.current = onCancel;
42
+ const confirmRef = React.useRef(onConfirm);
43
+ confirmRef.current = onConfirm;
44
+ const cancelRef = React.useRef(onCancel);
45
+ cancelRef.current = onCancel;
46
+ const setOpenRef = React.useRef(setOpen);
47
+ setOpenRef.current = setOpen;
58
48
 
59
- React.useEffect(() => {
60
- if (!isOpen) return;
61
- const prev = document.activeElement;
62
- const prevOverflow = document.body.style.overflow;
63
- document.body.style.overflow = "hidden";
64
- const focusables = () => (panel.current ? [...panel.current.querySelectorAll(FOCUSABLE)] : []);
65
- // Prefer the confirm action for destructive prompts (WAI-ARIA alertdialog).
66
- const preferred = panel.current?.querySelector(".cs-alert-dialog__confirm") || focusables()[0];
67
- (preferred || panel.current)?.focus?.();
68
- const k = (e) => {
69
- if (e.key === "Escape") {
70
- setOpen(false);
71
- cancelRef.current?.();
72
- return;
73
- }
74
- if (e.key !== "Tab" || !panel.current) return;
75
- const f = focusables();
76
- if (!f.length) { e.preventDefault(); panel.current.focus(); return; }
77
- const a = f[0], z = f[f.length - 1];
78
- const active = document.activeElement;
79
- const inside = panel.current.contains(active);
80
- if (e.shiftKey && (!inside || active === a)) { e.preventDefault(); z.focus(); }
81
- else if (!e.shiftKey && (!inside || active === z)) { e.preventDefault(); a.focus(); }
82
- };
83
- document.addEventListener("keydown", k);
84
- return () => {
85
- document.removeEventListener("keydown", k);
86
- document.body.style.overflow = prevOverflow;
87
- if (prev && prev.focus) prev.focus();
88
- };
89
- }, [isOpen]);
49
+ useOverlayLayer({
50
+ open: isOpen,
51
+ kind: "modal",
52
+ trapFocus: true,
53
+ preferFocusSelector: ".cs-alert-dialog__confirm",
54
+ onEscape: () => {
55
+ setOpenRef.current(false);
56
+ cancelRef.current?.();
57
+ },
58
+ panelRef: panel,
59
+ });
90
60
 
91
61
  const cl = cancelLabel != null ? cancelLabel : t("cancel");
92
62
  const cf = confirmLabel != null ? confirmLabel : t("confirm");
@@ -1,35 +1,39 @@
1
1
  import React from "react";
2
2
  import { makeT, useLang } from "../_i18n/i18n.js";
3
3
  import { cx } from "../_utils/cx.js";
4
+ import { useOverlayLayer } from "./OverlayManager.jsx";
4
5
 
5
6
  /** CyberSkill Drawer — slide-in side panel with scrim. side: right (default) | left. */
6
7
  export function Drawer({ open, onClose, title, side = "right", children, actions, lang, className }) {
7
8
  const [ref, L] = useLang(lang);
8
- React.useEffect(() => {
9
- if (!open) return;
10
- const prev = document.activeElement;
11
- const SEL = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
12
- const p = ref.current;
13
- const first = p ? p.querySelector(SEL) : null;
14
- (first || p) && (first || p).focus();
15
- const k = (e) => {
16
- if (e.key === "Escape") { onClose && onClose(); return; }
17
- if (e.key !== "Tab" || !ref.current) return;
18
- const f = [...ref.current.querySelectorAll(SEL)];
19
- if (!f.length) return;
20
- const a = f[0], z = f[f.length - 1];
21
- if (e.shiftKey && document.activeElement === a) { e.preventDefault(); z.focus(); }
22
- else if (!e.shiftKey && document.activeElement === z) { e.preventDefault(); a.focus(); }
23
- };
24
- document.addEventListener("keydown", k);
25
- return () => { document.removeEventListener("keydown", k); if (prev && prev.focus) prev.focus(); };
26
- }, [open]);
9
+ const panel = React.useRef(null);
10
+ const closeRef = React.useRef(onClose);
11
+ closeRef.current = onClose;
12
+
13
+ useOverlayLayer({
14
+ open: !!open,
15
+ kind: "modal",
16
+ trapFocus: true,
17
+ onEscape: () => closeRef.current && closeRef.current(),
18
+ panelRef: panel,
19
+ });
20
+
27
21
  const t = makeT("Drawer", L);
28
22
  if (!open) return null;
29
23
  return (
30
24
  <>
31
25
  <div className="cs-drawer-scrim" onClick={onClose} aria-hidden="true" />
32
- <aside ref={ref} tabIndex={-1} className={cx("cs-drawer", side === "left" && "cs-drawer--left", className)} role="dialog" aria-modal="true" aria-label={typeof title === "string" ? title : t("panel")}>
26
+ <aside
27
+ ref={(el) => {
28
+ panel.current = el;
29
+ ref.current = el;
30
+ }}
31
+ tabIndex={-1}
32
+ className={cx("cs-drawer", side === "left" && "cs-drawer--left", className)}
33
+ role="dialog"
34
+ aria-modal="true"
35
+ aria-label={typeof title === "string" ? title : t("panel")}
36
+ >
33
37
  <div className="cs-drawer__header">
34
38
  {title ? <h2 className="cs-drawer__title">{title}</h2> : null}
35
39
  <button type="button" className="cs-drawer__close" aria-label={t("close")} onClick={onClose}>
@@ -0,0 +1,40 @@
1
+ import type { ReactNode, RefObject } from "react";
2
+
3
+ export type OverlayLayerKind = "dropdown" | "popover" | "modal" | "toast" | "tour";
4
+
5
+ export type OverlayLayerRegistration = {
6
+ id?: string;
7
+ kind: OverlayLayerKind;
8
+ trapFocus?: boolean;
9
+ lockScroll?: boolean;
10
+ onEscape?: () => void;
11
+ restoreEl?: HTMLElement | null;
12
+ };
13
+
14
+ export type OverlayManagerApi = {
15
+ register(layer: OverlayLayerRegistration): () => void;
16
+ top(): OverlayLayerRegistration | null;
17
+ readonly scrollLocked: boolean;
18
+ readonly depth: number;
19
+ };
20
+
21
+ export function getOverlayManager(): OverlayManagerApi;
22
+
23
+ export function OverlayProvider(props: { children?: ReactNode }): JSX.Element;
24
+
25
+ export function useOverlayLayer(opts: {
26
+ open: boolean;
27
+ kind?: OverlayLayerKind;
28
+ trapFocus?: boolean;
29
+ lockScroll?: boolean;
30
+ onEscape?: () => void;
31
+ panelRef?: RefObject<HTMLElement | null>;
32
+ preferFocusSelector?: string;
33
+ }): { manager: OverlayManagerApi };
34
+
35
+ export function attachFocusTrap(
36
+ panelEl: HTMLElement | null,
37
+ opts?: { handleEscape?: boolean; onEscape?: () => void }
38
+ ): () => void;
39
+
40
+ export const focusableSelector: string;
@@ -0,0 +1,214 @@
1
+ import React from "react";
2
+
3
+ /** Shared focusable selector for modal traps (Dialog / AlertDialog / Drawer / CommandPalette). */
4
+ export const focusableSelector = [
5
+ "a[href]",
6
+ "area[href]",
7
+ "button:not([disabled])",
8
+ 'input:not([disabled]):not([type="hidden"])',
9
+ "select:not([disabled])",
10
+ "textarea:not([disabled])",
11
+ "audio[controls]",
12
+ "video[controls]",
13
+ "summary",
14
+ "iframe",
15
+ '[contenteditable]:not([contenteditable="false"])',
16
+ '[tabindex]:not([tabindex="-1"])',
17
+ ].join(",");
18
+
19
+ /**
20
+ * Attach Tab wrap + Escape (optional) to a panel. Returns cleanup.
21
+ * Prefer registering Escape via the overlay manager so nested modals stack correctly.
22
+ */
23
+ export function attachFocusTrap(panelEl, { handleEscape = false, onEscape } = {}) {
24
+ if (!panelEl) return () => {};
25
+ const focusables = () => [...panelEl.querySelectorAll(focusableSelector)];
26
+ const k = (e) => {
27
+ if (handleEscape && e.key === "Escape") {
28
+ onEscape && onEscape();
29
+ return;
30
+ }
31
+ if (e.key !== "Tab") return;
32
+ const f = focusables();
33
+ if (!f.length) {
34
+ e.preventDefault();
35
+ panelEl.focus();
36
+ return;
37
+ }
38
+ const a = f[0];
39
+ const z = f[f.length - 1];
40
+ const active = document.activeElement;
41
+ const inside = panelEl.contains(active);
42
+ if (e.shiftKey && (!inside || active === a)) {
43
+ e.preventDefault();
44
+ z.focus();
45
+ } else if (!e.shiftKey && (!inside || active === z)) {
46
+ e.preventDefault();
47
+ a.focus();
48
+ }
49
+ };
50
+ document.addEventListener("keydown", k);
51
+ return () => document.removeEventListener("keydown", k);
52
+ }
53
+
54
+ function createOverlayManager() {
55
+ const layers = [];
56
+ let prevOverflow = "";
57
+ let locked = false;
58
+ let escapeBound = false;
59
+
60
+ /** Innermost panel last — React child effects run before parents, so push-order alone is wrong. */
61
+ const resort = () => {
62
+ layers.sort((a, b) => {
63
+ const pa = a.panelEl;
64
+ const pb = b.panelEl;
65
+ if (pa && pb && pa !== pb) {
66
+ if (pa.contains(pb)) return -1; // a outer → below
67
+ if (pb.contains(pa)) return 1;
68
+ }
69
+ return (a.seq || 0) - (b.seq || 0);
70
+ });
71
+ };
72
+
73
+ const applyScroll = () => {
74
+ const need = layers.some((l) => l.kind === "modal" && l.lockScroll !== false);
75
+ if (need && !locked) {
76
+ prevOverflow = document.body.style.overflow;
77
+ document.body.style.overflow = "hidden";
78
+ locked = true;
79
+ } else if (!need && locked) {
80
+ document.body.style.overflow = prevOverflow;
81
+ locked = false;
82
+ }
83
+ };
84
+
85
+ const onDocEscape = (e) => {
86
+ if (e.key !== "Escape") return;
87
+ if (!layers.length) return;
88
+ resort();
89
+ const top = layers[layers.length - 1];
90
+ if (!top || typeof top.onEscape !== "function") return;
91
+ // Registered overlays (modals) always own Escape while on the stack.
92
+ // Menu/Popover/etc. are not registered here — they keep their own listeners.
93
+ e.preventDefault();
94
+ e.stopPropagation();
95
+ top.onEscape();
96
+ };
97
+
98
+ const ensureEscape = () => {
99
+ if (escapeBound) return;
100
+ escapeBound = true;
101
+ document.addEventListener("keydown", onDocEscape, true);
102
+ };
103
+
104
+ let seq = 0;
105
+ return {
106
+ register(layer) {
107
+ ensureEscape();
108
+ const id = layer.id || `cs-ov-${++seq}`;
109
+ const entry = { ...layer, id, seq: ++seq };
110
+ layers.push(entry);
111
+ resort();
112
+ applyScroll();
113
+ return () => {
114
+ resort();
115
+ const i = layers.findIndex((l) => l.id === id);
116
+ const wasTop = i === layers.length - 1;
117
+ if (i >= 0) layers.splice(i, 1);
118
+ applyScroll();
119
+ if (wasTop && entry.restoreEl && typeof entry.restoreEl.focus === "function") {
120
+ try {
121
+ entry.restoreEl.focus();
122
+ } catch {
123
+ /* ignore */
124
+ }
125
+ }
126
+ };
127
+ },
128
+ top() {
129
+ resort();
130
+ return layers[layers.length - 1] || null;
131
+ },
132
+ get scrollLocked() {
133
+ return locked;
134
+ },
135
+ get depth() {
136
+ return layers.length;
137
+ },
138
+ };
139
+ }
140
+
141
+ const defaultManager = createOverlayManager();
142
+ const OverlayContext = React.createContext(null);
143
+
144
+ /** Return the active manager (context or module singleton). */
145
+ export function getOverlayManager() {
146
+ return defaultManager;
147
+ }
148
+
149
+ /**
150
+ * Optional provider — mounts `#cs-overlay-root` for portals and supplies a
151
+ * dedicated manager instance. When omitted, components use the module singleton.
152
+ */
153
+ export function OverlayProvider({ children }) {
154
+ const api = React.useMemo(() => createOverlayManager(), []);
155
+ return (
156
+ <OverlayContext.Provider value={api}>
157
+ <div id="cs-overlay-root" data-cs-overlay-root="" />
158
+ {children}
159
+ </OverlayContext.Provider>
160
+ );
161
+ }
162
+
163
+ /**
164
+ * Register an overlay layer while `open` is true. Owns Escape (topmost only)
165
+ * and modal scroll-lock refcount. Tab trap is attached when `trapFocus` + `panelRef`.
166
+ */
167
+ export function useOverlayLayer({
168
+ open,
169
+ kind = "modal",
170
+ trapFocus = false,
171
+ lockScroll,
172
+ onEscape,
173
+ panelRef,
174
+ preferFocusSelector,
175
+ }) {
176
+ const ctx = React.useContext(OverlayContext);
177
+ const mgr = ctx || defaultManager;
178
+ const escapeRef = React.useRef(onEscape);
179
+ escapeRef.current = onEscape;
180
+
181
+ React.useLayoutEffect(() => {
182
+ if (!open) return undefined;
183
+ const restoreEl = typeof document !== "undefined" ? document.activeElement : null;
184
+ const panel = panelRef && panelRef.current;
185
+ const unregister = mgr.register({
186
+ kind,
187
+ lockScroll: lockScroll != null ? lockScroll : kind === "modal",
188
+ onEscape: () => escapeRef.current && escapeRef.current(),
189
+ restoreEl,
190
+ panelEl: panel || null,
191
+ });
192
+
193
+ // Only the innermost (top) layer should move focus / trap Tab — outer parents
194
+ // run layout effects after children and must not steal focus from nested alerts.
195
+ let detachTrap = () => {};
196
+ const top = mgr.top();
197
+ const isTop = top && top.panelEl === panel;
198
+ if (trapFocus && panel && isTop) {
199
+ const preferred =
200
+ (preferFocusSelector && panel.querySelector(preferFocusSelector)) ||
201
+ panel.querySelector(focusableSelector) ||
202
+ panel;
203
+ preferred && preferred.focus && preferred.focus();
204
+ detachTrap = attachFocusTrap(panel, { handleEscape: false });
205
+ }
206
+
207
+ return () => {
208
+ detachTrap();
209
+ unregister();
210
+ };
211
+ }, [open, kind, trapFocus, lockScroll, mgr, panelRef, preferFocusSelector]);
212
+
213
+ return { manager: mgr };
214
+ }
@@ -0,0 +1,12 @@
1
+ **OverlayProvider / overlay manager** — single stack for modal Escape, scroll-lock
2
+ refcount, and focus restore. Wrap product shells when nesting overlays; Dialog /
3
+ AlertDialog / Drawer / CommandPalette already register via the module singleton
4
+ when no provider is mounted.
5
+
6
+ ```jsx
7
+ <OverlayProvider>
8
+ <App />
9
+ </OverlayProvider>
10
+ ```
11
+
12
+ See `docs/decisions/overlay-manager-design.md`.
@@ -6,30 +6,37 @@ import { cx } from "../_utils/cx.js";
6
6
  export function Tour({ steps = [], open, onClose, lang, className }) {
7
7
  const [i, setI] = React.useState(0);
8
8
  const [rect, setRect] = React.useState(null);
9
+ const [pop, setPop] = React.useState({ top: 80, left: 40 });
9
10
  const [ref, L] = useLang(lang);
10
11
  const t = makeT("Tour", L);
11
12
  React.useEffect(() => { if (open) setI(0); }, [open]);
12
13
  React.useEffect(() => {
13
- if (!open || !steps[i]) return;
14
+ if (!open || !steps[i]) return undefined;
14
15
  const el = document.querySelector(steps[i].target);
15
16
  if (el) {
16
17
  const r = el.getBoundingClientRect();
17
- setRect({ x: r.left - 6, y: r.top - 6, w: r.width + 12, h: r.height + 12 });
18
- } else setRect(null);
18
+ const next = { x: r.left - 6, y: r.top - 6, w: r.width + 12, h: r.height + 12 };
19
+ setRect(next);
20
+ setPop({
21
+ top: Math.min(window.innerHeight - 170, next.y + next.h + 12),
22
+ left: Math.max(12, Math.min(window.innerWidth - 292, next.x)),
23
+ });
24
+ } else {
25
+ setRect(null);
26
+ setPop({ top: 80, left: 40 });
27
+ }
19
28
  const k = (e) => { if (e.key === "Escape") onClose && onClose(); };
20
29
  document.addEventListener("keydown", k);
21
30
  return () => document.removeEventListener("keydown", k);
22
- }, [open, i, steps]);
31
+ }, [open, i, steps, onClose]);
23
32
  if (!open || !steps.length) return <span ref={ref} style={{ display: "none" }} />;
24
33
  const s = steps[i];
25
34
  const last = i === steps.length - 1;
26
- const popTop = rect ? Math.min(window.innerHeight - 170, rect.y + rect.h + 12) : 80;
27
- const popLeft = rect ? Math.max(12, Math.min(window.innerWidth - 292, rect.x)) : 40;
28
35
  return (
29
36
  <div ref={ref} className={cx("cs-tour", className)}>
30
37
  <div className="cs-tour__scrim" onClick={onClose} />
31
38
  {rect ? <div className="cs-tour__hole" style={{ left: rect.x, top: rect.y, width: rect.w, height: rect.h }} /> : null}
32
- <div className="cs-tour__pop" role="dialog" aria-label={typeof s.title === "string" ? s.title : undefined} style={{ left: popLeft, top: popTop }}>
39
+ <div className="cs-tour__pop" role="dialog" aria-label={typeof s.title === "string" ? s.title : undefined} style={{ left: pop.left, top: pop.top }}>
33
40
  <b>{s.title}</b>
34
41
  {s.body ? <p>{s.body}</p> : null}
35
42
  <div className="cs-tour__bar">