@morscherlab/mint-sdk 1.4.0-beta.2 → 1.4.0-beta.20

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 (330) hide show
  1. package/README.md +14 -3
  2. package/dist/{ActionMenu-CScINahU.js → AlertBox-CzLg0UaL.js} +40 -209
  3. package/dist/AlertBox-CzLg0UaL.js.map +1 -0
  4. package/dist/{AppSidebar-BmiiPfz9.js → AppSidebar-6oTT-gPO.js} +1249 -239
  5. package/dist/AppSidebar-6oTT-gPO.js.map +1 -0
  6. package/dist/{ConfirmDialog-CJ7hqYUP.js → ConfirmDialog-CSls2Nuv.js} +2 -2
  7. package/dist/{ConfirmDialog-CJ7hqYUP.js.map → ConfirmDialog-CSls2Nuv.js.map} +1 -1
  8. package/dist/ExperimentPopover-9l4QZlvQ.js +337 -0
  9. package/dist/ExperimentPopover-9l4QZlvQ.js.map +1 -0
  10. package/dist/{ExperimentSelectorModal-D-9z7cNx.js → ExperimentSelectorModal-pR5fK3pA.js} +244 -211
  11. package/dist/ExperimentSelectorModal-pR5fK3pA.js.map +1 -0
  12. package/dist/{SegmentedControl-DERBN-Xd.js → SegmentedControl-CJ7EAmOq.js} +2 -2
  13. package/dist/{SegmentedControl-DERBN-Xd.js.map → SegmentedControl-CJ7EAmOq.js.map} +1 -1
  14. package/dist/{SettingsModal-CSLE31yg.js → SettingsModal-Dyitgzfp.js} +28 -27
  15. package/dist/SettingsModal-Dyitgzfp.js.map +1 -0
  16. package/dist/__stories__/experiment-helpers.d.ts +10 -0
  17. package/dist/__tests__/components/AppTopBarOnboarding.test.d.ts +1 -0
  18. package/dist/__tests__/composables/useOnboarding.test.d.ts +1 -0
  19. package/dist/__tests__/styles/paletteTokens.test.d.ts +1 -0
  20. package/dist/__tests__/styles/v2AuditRules.test.d.ts +1 -0
  21. package/dist/components/AppSidebar.vue.d.ts +1 -1
  22. package/dist/components/AppTopBar.vue.d.ts +20 -5
  23. package/dist/components/ArtifactSaveDialog.vue.d.ts +9 -1
  24. package/dist/components/AuditTrail.vue.d.ts +1 -1
  25. package/dist/components/BaseButton.vue.d.ts +4 -2
  26. package/dist/components/BaseSelect.vue.d.ts +2 -2
  27. package/dist/components/ChartContainer.vue.d.ts +2 -0
  28. package/dist/components/DatePicker.vue.d.ts +1 -1
  29. package/dist/components/ExperimentPopover.vue.d.ts +18 -0
  30. package/dist/components/ExperimentSelectorModal.vue.d.ts +15 -5
  31. package/dist/components/FilePicker.vue.d.ts +1 -1
  32. package/dist/components/FileUploader.vue.d.ts +2 -2
  33. package/dist/components/JobsStatusTray.vue.d.ts +15 -4
  34. package/dist/components/PluginIcon.vue.d.ts +2 -2
  35. package/dist/components/PluginWorkspaceView.props.d.ts +3 -0
  36. package/dist/components/PluginWorkspaceView.vue.d.ts +3 -0
  37. package/dist/components/SampleSelector.vue.d.ts +6 -6
  38. package/dist/components/SequenceProgressBar.vue.d.ts +3 -0
  39. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -1
  40. package/dist/components/StepWizard.vue.d.ts +3 -0
  41. package/dist/components/internal/AppTopBarOnboardingInternal.vue.d.ts +7 -0
  42. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -1
  43. package/dist/components/internal/AutoGroupModalInternal.vue.d.ts +1 -1
  44. package/dist/components/internal/DatePickerTimeInternal.vue.d.ts +1 -1
  45. package/dist/components/internal/OnboardingLayerInternal.vue.d.ts +7 -0
  46. package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +2 -7
  47. package/dist/components/internal/SelectMultipleInternal.vue.d.ts +4 -0
  48. package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +8 -0
  49. package/dist/composables/createClient.d.ts +2 -0
  50. package/dist/composables/index.d.ts +1 -0
  51. package/dist/composables/pluginIdentity.d.ts +24 -0
  52. package/dist/composables/pluginWorkspaceModel.d.ts +2 -2
  53. package/dist/composables/useAppExperiment.d.ts +8 -0
  54. package/dist/composables/useCalendarGrid.d.ts +1 -1
  55. package/dist/composables/useExperimentSelector.d.ts +6 -2
  56. package/dist/composables/useJobCenter.d.ts +41 -3
  57. package/dist/composables/useOnboarding.d.ts +119 -0
  58. package/dist/composables/usePlatform.d.ts +1 -0
  59. package/dist/{controlSchemaModel-CP5-_nAj.js → controlSchemaModel-DCUOx8ll.js} +2 -2
  60. package/dist/{controlSchemaModel-CP5-_nAj.js.map → controlSchemaModel-DCUOx8ll.js.map} +1 -1
  61. package/dist/{data-C2PwI_dn.js → data-DBnmeBZX.js} +179 -125
  62. package/dist/data-DBnmeBZX.js.map +1 -0
  63. package/dist/data.js +8 -8
  64. package/dist/{experiment-43OJtH7R.js → experiment-DyO54FQB.js} +25 -61
  65. package/dist/experiment-DyO54FQB.js.map +1 -0
  66. package/dist/{experimentStatus-BZmU4czV.js → experimentStatus-BFrKpHAH.js} +3 -3
  67. package/dist/{experimentStatus-BZmU4czV.js.map → experimentStatus-BFrKpHAH.js.map} +1 -1
  68. package/dist/index.js +20 -20
  69. package/dist/{install-DAID6c2-.js → install-qjELyNwc.js} +15 -14
  70. package/dist/{install-DAID6c2-.js.map → install-qjELyNwc.js.map} +1 -1
  71. package/dist/install.js +1 -1
  72. package/dist/{lab-DLwRl_Ql.js → lab-UyTbokQC.js} +136 -148
  73. package/dist/lab-UyTbokQC.js.map +1 -0
  74. package/dist/lab.js +4 -3
  75. package/dist/{records-BzTcxekG.js → records-CR_pYzIP.js} +533 -335
  76. package/dist/records-CR_pYzIP.js.map +1 -0
  77. package/dist/{runtime-D6gVat6r.js → runtime-CoHhBP10.js} +417 -54
  78. package/dist/runtime-CoHhBP10.js.map +1 -0
  79. package/dist/runtime.d.ts +1 -1
  80. package/dist/runtime.js +7 -7
  81. package/dist/{shell-5ju97nOS.js → shell-BDuZf2zb.js} +10 -5
  82. package/dist/{shell-5ju97nOS.js.map → shell-BDuZf2zb.js.map} +1 -1
  83. package/dist/shell.d.ts +1 -0
  84. package/dist/shell.js +6 -6
  85. package/dist/stores/index.d.ts +1 -1
  86. package/dist/stores/index.js +2 -2
  87. package/dist/stores/settings.d.ts +4 -12
  88. package/dist/styles.css +2732 -2083
  89. package/dist/types/componentWorkflowTypes.d.ts +2 -0
  90. package/dist/types/components.d.ts +2 -0
  91. package/dist/types/generated/platformContext.d.ts +1 -0
  92. package/dist/types/generated-ui.d.ts +4 -0
  93. package/dist/types/index.d.ts +1 -1
  94. package/dist/types/jobs.d.ts +9 -0
  95. package/dist/types/platform.d.ts +1 -1
  96. package/dist/{BaseModal-DfRIuVoj.js → useDropdownState-Bb2N4m40.js} +74 -4
  97. package/dist/useDropdownState-Bb2N4m40.js.map +1 -0
  98. package/dist/{useExperimentSamples-CxcnxZ8h.js → useExperimentSamples-Dno1fFMq.js} +4 -4
  99. package/dist/{useExperimentSamples-CxcnxZ8h.js.map → useExperimentSamples-Dno1fFMq.js.map} +1 -1
  100. package/dist/{useListReorder-CcbEYxX-.js → useListReorder-CQ9OCScc.js} +183 -351
  101. package/dist/useListReorder-CQ9OCScc.js.map +1 -0
  102. package/dist/{useRackEditor-BD5NIo5s.js → useRackEditor-wL3kPrRT.js} +73 -59
  103. package/dist/useRackEditor-wL3kPrRT.js.map +1 -0
  104. package/dist/useRealtime-C3whhVvb.js +508 -0
  105. package/dist/useRealtime-C3whhVvb.js.map +1 -0
  106. package/dist/useRequest-CDkrXNU7.js +244 -0
  107. package/dist/useRequest-CDkrXNU7.js.map +1 -0
  108. package/dist/utils/time.d.ts +7 -0
  109. package/package.json +1 -1
  110. package/src/__stories__/experiment-helpers.ts +3 -3
  111. package/src/__tests__/components/AppTopBar.test.ts +257 -3
  112. package/src/__tests__/components/AppTopBarOnboarding.test.ts +160 -0
  113. package/src/__tests__/components/AppTopBarPageSelector.test.ts +125 -9
  114. package/src/__tests__/components/AppTopBarPillNav.test.ts +35 -0
  115. package/src/__tests__/components/ArtifactSaveDialog.test.ts +78 -0
  116. package/src/__tests__/components/BaseButton.test.ts +19 -0
  117. package/src/__tests__/components/BaseSelect.multiple.test.ts +87 -32
  118. package/src/__tests__/components/BaseSelect.test.ts +2 -1
  119. package/src/__tests__/components/BatchProgressList.test.ts +16 -0
  120. package/src/__tests__/components/CalendarGridPanel.test.ts +2 -2
  121. package/src/__tests__/components/ChartContainer.test.ts +11 -0
  122. package/src/__tests__/components/ChemicalFormula.test.ts +6 -3
  123. package/src/__tests__/components/DatePicker.datetime.test.ts +6 -0
  124. package/src/__tests__/components/DatePicker.test.ts +21 -0
  125. package/src/__tests__/components/DatePicker.time.test.ts +2 -2
  126. package/src/__tests__/components/EmptyState.test.ts +1 -1
  127. package/src/__tests__/components/ExperimentPopover.test.ts +125 -16
  128. package/src/__tests__/components/ExperimentSelectorModal.test.ts +153 -14
  129. package/src/__tests__/components/GeneratedPluginView.test.ts +23 -2
  130. package/src/__tests__/components/InstrumentStateBadge.test.ts +4 -4
  131. package/src/__tests__/components/JobsStatusTray.test.ts +373 -71
  132. package/src/__tests__/components/PluginWorkspaceView.test.ts +63 -1
  133. package/src/__tests__/components/ResourcePickerListInternal.test.ts +0 -20
  134. package/src/__tests__/components/SampleSelector.test.ts +33 -8
  135. package/src/__tests__/components/SequenceProgressBar.test.ts +12 -0
  136. package/src/__tests__/components/SettingsModal.test.ts +9 -2
  137. package/src/__tests__/components/StepWizard.test.ts +11 -0
  138. package/src/__tests__/components/WellPlate.decoration.test.ts +11 -4
  139. package/src/__tests__/components/deprecationTags.test.ts +1 -1
  140. package/src/__tests__/composables/useAppExperiment.test.ts +27 -1
  141. package/src/__tests__/composables/useCalendarGrid.test.ts +4 -2
  142. package/src/__tests__/composables/useExperimentSelector.test.ts +111 -0
  143. package/src/__tests__/composables/useJobCenter.test.ts +262 -3
  144. package/src/__tests__/composables/useOnboarding.test.ts +317 -0
  145. package/src/__tests__/docs/frontendDocsCatalog.test.ts +4 -3
  146. package/src/__tests__/stores/settings.test.ts +21 -21
  147. package/src/__tests__/styles/baseControlsRules.test.ts +6 -2
  148. package/src/__tests__/styles/controlRefreshRules.test.ts +0 -6
  149. package/src/__tests__/styles/cssTokens.test.ts +6 -3
  150. package/src/__tests__/styles/menusShellRefresh.test.ts +22 -12
  151. package/src/__tests__/styles/paletteTokens.test.ts +44 -0
  152. package/src/__tests__/styles/v2AuditRules.test.ts +104 -0
  153. package/src/__tests__/utils/plotlyTemplate.test.ts +4 -4
  154. package/src/__tests__/utils/time.test.ts +18 -1
  155. package/src/components/AdductText.story.vue +7 -0
  156. package/src/components/AdductText.vue +1 -1
  157. package/src/components/AlertBox.story.vue +2 -2
  158. package/src/components/AlertBox.vue +18 -16
  159. package/src/components/AppSidebar.story.vue +0 -28
  160. package/src/components/AppTopBar.onboarding.story.vue +69 -0
  161. package/src/components/AppTopBar.story.vue +105 -15
  162. package/src/components/AppTopBar.vue +248 -122
  163. package/src/components/ArtifactSaveDialog.story.vue +23 -0
  164. package/src/components/ArtifactSaveDialog.vue +102 -12
  165. package/src/components/AuditTrail.story.vue +2 -1
  166. package/src/components/AuditTrail.vue +2 -21
  167. package/src/components/Avatar.vue +10 -14
  168. package/src/components/BaseButton.vue +16 -2
  169. package/src/components/BaseSelect.multiple.story.vue +13 -2
  170. package/src/components/BaseSelect.vue +4 -3
  171. package/src/components/BatchProgressList.vue +35 -25
  172. package/src/components/ChartContainer.story.vue +2 -1
  173. package/src/components/ChartContainer.vue +8 -6
  174. package/src/components/ConfirmDialog.story.vue +1 -1
  175. package/src/components/EmptyState.vue +1 -1
  176. package/src/components/ExperimentPopover.story.vue +165 -53
  177. package/src/components/ExperimentPopover.vue +169 -187
  178. package/src/components/ExperimentSelectorModal.story.vue +73 -31
  179. package/src/components/ExperimentSelectorModal.vue +144 -111
  180. package/src/components/FormField.story.vue +8 -48
  181. package/src/components/GeneratedPluginView.vue +11 -10
  182. package/src/components/GeneratedResultRenderer.vue +3 -4
  183. package/src/components/InstrumentStateBadge.vue +1 -1
  184. package/src/components/JobsStatusTray.story.vue +97 -43
  185. package/src/components/JobsStatusTray.vue +287 -290
  186. package/src/components/LayoutResizeHandle.story.vue +65 -4
  187. package/src/components/LoadingSpinner.vue +1 -1
  188. package/src/components/NumberInput.vue +3 -6
  189. package/src/components/PluginIcon.story.vue +2 -10
  190. package/src/components/PluginIcon.vue +2 -2
  191. package/src/components/PluginWorkspaceView.props.ts +3 -0
  192. package/src/components/PluginWorkspaceView.story.vue +1 -1
  193. package/src/components/PluginWorkspaceView.vue +17 -0
  194. package/src/components/RuntimeUpdateNotice.vue +1 -1
  195. package/src/components/SampleSelector.story.vue +22 -7
  196. package/src/components/SampleSelector.vue +34 -37
  197. package/src/components/SampleSelectorGroupNode.vue +1 -1
  198. package/src/components/SectionCard.story.vue +35 -2
  199. package/src/components/SequenceProgressBar.story.vue +9 -0
  200. package/src/components/SequenceProgressBar.vue +6 -1
  201. package/src/components/SettingsModal.schema.ts +1 -1
  202. package/src/components/SettingsModal.vue +9 -14
  203. package/src/components/SmartGroupFieldRecipe.story.vue +1 -1
  204. package/src/components/SmartGroupManual.story.vue +1 -1
  205. package/src/components/StepWizard.story.vue +33 -0
  206. package/src/components/StepWizard.vue +8 -1
  207. package/src/components/WellPlate.story.vue +3 -1
  208. package/src/components/WellPlate.vue +7 -0
  209. package/src/components/internal/AppTopBarOnboardingInternal.vue +172 -0
  210. package/src/components/internal/AppTopBarPageSelectorInternal.vue +140 -72
  211. package/src/components/internal/AppTopBarPillNavInternal.vue +33 -2
  212. package/src/components/internal/DatePickerDateInternal.vue +16 -40
  213. package/src/components/internal/DatePickerDateTimeInternal.vue +3 -13
  214. package/src/components/internal/DatePickerTimeInternal.vue +5 -8
  215. package/src/components/internal/OnboardingLayerInternal.vue +173 -0
  216. package/src/components/internal/ResourcePickerListInternal.vue +45 -85
  217. package/src/components/internal/SelectMultipleInternal.vue +182 -65
  218. package/src/components/internal/SelectSearchableInternal.vue +2 -3
  219. package/src/components/internal/WorkspaceShellInternal.vue +12 -0
  220. package/src/composables/createClient.ts +2 -0
  221. package/src/composables/index.ts +1 -0
  222. package/src/composables/pluginIdentity.ts +31 -0
  223. package/src/composables/pluginWorkspaceModel.ts +2 -2
  224. package/src/composables/useAppExperiment.ts +20 -0
  225. package/src/composables/useCalendarGrid.ts +4 -3
  226. package/src/composables/useExperimentSelector.ts +77 -31
  227. package/src/composables/useJobCenter.ts +188 -6
  228. package/src/composables/useOnboarding.ts +465 -0
  229. package/src/composables/usePluginWorkspace.ts +1 -1
  230. package/src/runtime.ts +2 -1
  231. package/src/shell.ts +17 -0
  232. package/src/stores/index.ts +0 -2
  233. package/src/stores/settings.ts +20 -50
  234. package/src/styles/components/adduct-text.css +13 -5
  235. package/src/styles/components/alert-box.css +39 -58
  236. package/src/styles/components/app-layout.css +7 -5
  237. package/src/styles/components/app-page-selector.css +121 -44
  238. package/src/styles/components/app-pill-nav.css +11 -12
  239. package/src/styles/components/app-sidebar.css +9 -18
  240. package/src/styles/components/app-top-bar.css +219 -55
  241. package/src/styles/components/artifact-save-dialog.css +153 -2
  242. package/src/styles/components/audit-trail.css +16 -19
  243. package/src/styles/components/avatar.css +6 -4
  244. package/src/styles/components/batch-progress-list.css +38 -36
  245. package/src/styles/components/button.css +36 -59
  246. package/src/styles/components/calendar.css +12 -12
  247. package/src/styles/components/chart-container.css +34 -23
  248. package/src/styles/components/checkbox.css +2 -12
  249. package/src/styles/components/collapsible-card.css +27 -29
  250. package/src/styles/components/confirm-dialog.css +13 -14
  251. package/src/styles/components/dataframe.css +13 -5
  252. package/src/styles/components/date-picker.css +80 -92
  253. package/src/styles/components/datetime-picker.css +31 -43
  254. package/src/styles/components/divider.css +20 -20
  255. package/src/styles/components/dose-calculator.css +3 -3
  256. package/src/styles/components/empty-state.css +20 -17
  257. package/src/styles/components/experiment-code-badge.css +2 -0
  258. package/src/styles/components/experiment-popover.css +174 -329
  259. package/src/styles/components/experiment-selector-modal.css +88 -56
  260. package/src/styles/components/file-picker.css +3 -3
  261. package/src/styles/components/file-uploader.css +38 -49
  262. package/src/styles/components/form-builder.css +3 -3
  263. package/src/styles/components/formula-input.css +1 -1
  264. package/src/styles/components/icon-button.css +18 -24
  265. package/src/styles/components/input.css +5 -4
  266. package/src/styles/components/instrument-monitor.css +13 -15
  267. package/src/styles/components/jobs-status-tray.css +140 -296
  268. package/src/styles/components/layout-resize-handle.css +59 -16
  269. package/src/styles/components/modal.css +4 -6
  270. package/src/styles/components/molecule-input.css +2 -2
  271. package/src/styles/components/multi-select.css +65 -117
  272. package/src/styles/components/number-input.css +26 -19
  273. package/src/styles/components/onboarding.css +368 -0
  274. package/src/styles/components/pill.css +3 -2
  275. package/src/styles/components/plate-editor.css +0 -1
  276. package/src/styles/components/plugin-icon.css +26 -23
  277. package/src/styles/components/popover.css +2 -2
  278. package/src/styles/components/presence-avatars.css +1 -1
  279. package/src/styles/components/progress-bar.css +3 -3
  280. package/src/styles/components/protocol-step-editor.css +1 -3
  281. package/src/styles/components/resource-card.css +2 -2
  282. package/src/styles/components/resource-picker-list.css +6 -57
  283. package/src/styles/components/sample-hierarchy-tree.css +4 -4
  284. package/src/styles/components/sample-selector.css +138 -79
  285. package/src/styles/components/schedule-calendar.css +8 -8
  286. package/src/styles/components/searchable-select.css +12 -12
  287. package/src/styles/components/section-card.css +54 -49
  288. package/src/styles/components/segmented-control.css +18 -15
  289. package/src/styles/components/select.css +3 -3
  290. package/src/styles/components/sequence-input.css +2 -2
  291. package/src/styles/components/sequence-progress-bar.css +17 -8
  292. package/src/styles/components/settings-modal.css +7 -2
  293. package/src/styles/components/smart-group.css +18 -18
  294. package/src/styles/components/step-wizard.css +52 -16
  295. package/src/styles/components/tabs.css +2 -2
  296. package/src/styles/components/textarea.css +3 -3
  297. package/src/styles/components/time-picker.css +36 -21
  298. package/src/styles/components/toast.css +12 -10
  299. package/src/styles/components/toggle.css +1 -11
  300. package/src/styles/components/tooltip.css +10 -9
  301. package/src/styles/components/unit-input.css +8 -7
  302. package/src/styles/components/well-plate.css +9 -2
  303. package/src/styles/index.css +1 -0
  304. package/src/styles/variables.css +241 -78
  305. package/src/types/componentWorkflowTypes.ts +2 -0
  306. package/src/types/components.ts +2 -0
  307. package/src/types/generated/platformContext.ts +1 -0
  308. package/src/types/generated-ui.ts +4 -0
  309. package/src/types/index.ts +1 -0
  310. package/src/types/jobs.ts +10 -0
  311. package/src/types/platform.ts +1 -1
  312. package/src/utils/time.ts +21 -0
  313. package/dist/ActionMenu-CScINahU.js.map +0 -1
  314. package/dist/AppSidebar-BmiiPfz9.js.map +0 -1
  315. package/dist/BaseModal-DfRIuVoj.js.map +0 -1
  316. package/dist/ExperimentPopover-C4sGw-is.js +0 -359
  317. package/dist/ExperimentPopover-C4sGw-is.js.map +0 -1
  318. package/dist/ExperimentSelectorModal-D-9z7cNx.js.map +0 -1
  319. package/dist/SettingsModal-CSLE31yg.js.map +0 -1
  320. package/dist/data-C2PwI_dn.js.map +0 -1
  321. package/dist/experiment-43OJtH7R.js.map +0 -1
  322. package/dist/lab-DLwRl_Ql.js.map +0 -1
  323. package/dist/records-BzTcxekG.js.map +0 -1
  324. package/dist/runtime-D6gVat6r.js.map +0 -1
  325. package/dist/useListReorder-CcbEYxX-.js.map +0 -1
  326. package/dist/useRackEditor-BD5NIo5s.js.map +0 -1
  327. package/dist/useRealtime-PalKue84.js +0 -696
  328. package/dist/useRealtime-PalKue84.js.map +0 -1
  329. package/dist/useRequest-D5JDfsgZ.js +0 -76
  330. package/dist/useRequest-D5JDfsgZ.js.map +0 -1
@@ -23,6 +23,7 @@ function initState() {
23
23
  showProgress: true,
24
24
  showStepNumbers: true,
25
25
  size: 'md' as const,
26
+ orientation: 'horizontal' as const,
26
27
  }
27
28
  }
28
29
  </script>
@@ -39,6 +40,7 @@ function initState() {
39
40
  :show-progress="state.showProgress"
40
41
  :show-step-numbers="state.showStepNumbers"
41
42
  :size="state.size"
43
+ :orientation="state.orientation"
42
44
  >
43
45
  <template #step-setup>
44
46
  <div style="padding: 1.5rem;">
@@ -72,6 +74,14 @@ function initState() {
72
74
  <HstCheckbox v-model="state.linear" title="Linear" />
73
75
  <HstCheckbox v-model="state.showProgress" title="Show Progress" />
74
76
  <HstCheckbox v-model="state.showStepNumbers" title="Show Step Numbers" />
77
+ <HstSelect
78
+ v-model="state.orientation"
79
+ title="Orientation"
80
+ :options="[
81
+ { value: 'horizontal', label: 'Horizontal' },
82
+ { value: 'vertical', label: 'Vertical' },
83
+ ]"
84
+ />
75
85
  <HstSelect
76
86
  v-model="state.size"
77
87
  title="Size"
@@ -129,6 +139,29 @@ function initState() {
129
139
  </div>
130
140
  </Variant>
131
141
 
142
+ <Variant title="Vertical">
143
+ <div style="padding: 2rem; max-width: 700px;">
144
+ <StepWizard
145
+ :steps="mockSteps"
146
+ :model-value="2"
147
+ orientation="vertical"
148
+ >
149
+ <template #step-setup>
150
+ <div style="padding: 1.5rem;">Setup</div>
151
+ </template>
152
+ <template #step-samples>
153
+ <div style="padding: 1.5rem;">Samples</div>
154
+ </template>
155
+ <template #step-protocol>
156
+ <div style="padding: 1.5rem;">Protocol (active)</div>
157
+ </template>
158
+ <template #step-review>
159
+ <div style="padding: 1.5rem;">Review</div>
160
+ </template>
161
+ </StepWizard>
162
+ </div>
163
+ </Variant>
164
+
132
165
  <Variant title="Small">
133
166
  <div style="padding: 2rem; max-width: 500px;">
134
167
  <StepWizard
@@ -11,6 +11,8 @@ export interface Props {
11
11
  showProgress?: boolean
12
12
  showStepNumbers?: boolean
13
13
  size?: 'sm' | 'md' | 'lg'
14
+ /** `vertical` stacks the steps in a rail beside the step content. */
15
+ orientation?: 'horizontal' | 'vertical'
14
16
  }
15
17
 
16
18
  const props = withDefaults(defineProps<Props>(), {
@@ -19,6 +21,7 @@ const props = withDefaults(defineProps<Props>(), {
19
21
  showProgress: true,
20
22
  showStepNumbers: true,
21
23
  size: 'md',
24
+ orientation: 'horizontal',
22
25
  })
23
26
 
24
27
  const emit = defineEmits<{
@@ -114,7 +117,11 @@ defineExpose({ setStepValid })
114
117
 
115
118
  <template>
116
119
  <div
117
- :class="['mint-wizard', `mint-wizard--${props.size}`]"
120
+ :class="[
121
+ 'mint-wizard',
122
+ `mint-wizard--${props.size}`,
123
+ { 'mint-wizard--vertical': props.orientation === 'vertical' },
124
+ ]"
118
125
  tabindex="0"
119
126
  @keydown="handleKeydown"
120
127
  >
@@ -64,13 +64,14 @@ function generateHeatmapWells(format: WellPlateFormat): Record<string, Partial<W
64
64
  const mockWells96 = generateMockWells(96)
65
65
  const heatmapWells = generateHeatmapWells(96)
66
66
  const mockWells6 = generateMockWells(6)
67
- // One well per marker type; the Q / i glyph moves to a corner when an ID or label takes the centre.
67
+ // One well per marker type; a QC well shows a mono Q and an iQC well an i when no ID or label takes the centre.
68
68
  const markerWells: Record<string, Partial<Well>> = {
69
69
  A1: { id: 'A1', row: 0, col: 0, state: 'filled', sampleType: 'sample', metadata: { label: 'S-01' } },
70
70
  A2: { id: 'A2', row: 0, col: 1, state: 'filled', sampleType: 'blank', metadata: { label: 'Blank' } },
71
71
  A3: { id: 'A3', row: 0, col: 2, state: 'filled', sampleType: 'qc', metadata: { label: 'Pool' } },
72
72
  B1: { id: 'B1', row: 1, col: 0, state: 'filled', sampleType: 'iqc', metadata: { label: 'Std 1' } },
73
73
  B2: { id: 'B2', row: 1, col: 1, state: 'filled', sampleType: 'iqc' },
74
+ B3: { id: 'B3', row: 1, col: 2, state: 'filled', sampleType: 'qc' },
74
75
  }
75
76
 
76
77
  const rectSelected = ref<string[]>([])
@@ -287,6 +288,7 @@ function initState() {
287
288
  <div style="padding: 2rem; display: flex; gap: 2rem;">
288
289
  <WellPlate :spec="{ format: 6, selectionMode: 'none', showWellIds: true, size: 'lg' }" :wells="markerWells" />
289
290
  <WellPlate :spec="{ format: 6, selectionMode: 'none', showWellLabels: true, size: 'lg' }" :wells="markerWells" />
291
+ <WellPlate :spec="{ format: 6, selectionMode: 'none', size: 'lg' }" :wells="markerWells" />
290
292
  </div>
291
293
  </Variant>
292
294
 
@@ -449,6 +449,13 @@ const tableStyle = computed(() => ({
449
449
  >
450
450
  {{ well.id }}
451
451
  </span>
452
+ <!-- QC / iQC marker glyph when the centre is free -->
453
+ <span
454
+ v-else-if="well.sampleType === 'qc' || well.sampleType === 'iqc'"
455
+ class="mint-well-plate__indicator"
456
+ >
457
+ {{ well.sampleType === 'qc' ? 'Q' : 'i' }}
458
+ </span>
452
459
 
453
460
  <!-- Badge (injection count or custom method) -->
454
461
  <span
@@ -0,0 +1,172 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * The top-bar onboarding controls: the "Getting started n/m" chip while the
4
+ * checklist is open, and the Help button with the checklist, tours and news.
5
+ */
6
+ import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
7
+ import { autoUpdate, computePosition, offset, shift } from '@floating-ui/dom'
8
+ import { Check, CircleHelp, Play } from 'lucide-vue-next'
9
+ import { useEventListener } from '../../composables/useEventListener'
10
+ import type { OnboardingState } from '../../composables/useOnboarding'
11
+ import OnboardingLayerInternal from './OnboardingLayerInternal.vue'
12
+
13
+ const props = defineProps<{ state: OnboardingState }>()
14
+
15
+ const root = ref<HTMLElement | null>(null)
16
+ const helpButton = ref<HTMLElement | null>(null)
17
+ const panel = ref<HTMLElement | null>(null)
18
+ const panelPosition = ref({ x: 0, y: 0 })
19
+ const open = ref(false)
20
+ const hasContent = computed(() =>
21
+ props.state.tasks.value.length > 0 || props.state.tours.value.length > 0 || props.state.news.value.length > 0)
22
+ const progress = computed(() => {
23
+ const total = props.state.tasks.value.length
24
+ return total ? Math.round((props.state.tasksDone.value / total) * 100) : 0
25
+ })
26
+
27
+ function toggle() {
28
+ open.value = !open.value
29
+ if (open.value) props.state.refresh()
30
+ }
31
+
32
+ // The panel goes to the body, as the top bar clips its overflow; it follows the button.
33
+ let stopAutoUpdate: (() => void) | undefined
34
+ watch(open, async (isOpen) => {
35
+ stopAutoUpdate?.()
36
+ stopAutoUpdate = undefined
37
+ if (!isOpen) return
38
+ await nextTick()
39
+ const button = helpButton.value
40
+ const floating = panel.value
41
+ if (!button || !floating || !open.value) return
42
+ stopAutoUpdate = autoUpdate(button, floating, async () => {
43
+ const { x, y } = await computePosition(button, floating, {
44
+ strategy: 'fixed',
45
+ placement: 'bottom-end',
46
+ middleware: [offset(8), shift({ padding: 8 })],
47
+ })
48
+ panelPosition.value = { x, y }
49
+ })
50
+ })
51
+ onBeforeUnmount(() => stopAutoUpdate?.())
52
+
53
+ function run(action: () => void) {
54
+ open.value = false
55
+ action()
56
+ }
57
+
58
+ useEventListener(document, 'pointerdown', (event) => {
59
+ const target = event.target as Node
60
+ if (open.value && !root.value?.contains(target) && !panel.value?.contains(target)) open.value = false
61
+ })
62
+ useEventListener(document, 'keydown', (event) => {
63
+ if (open.value && event.key === 'Escape') open.value = false
64
+ })
65
+ </script>
66
+
67
+ <template>
68
+ <div v-if="hasContent" ref="root" class="mint-onboarding-help">
69
+ <button
70
+ v-if="state.checklistVisible.value"
71
+ type="button"
72
+ class="mint-onboarding-help__chip"
73
+ :class="{ 'mint-onboarding-help__chip--open': open }"
74
+ data-onboarding="topbar-checklist"
75
+ :aria-expanded="open"
76
+ @click="toggle"
77
+ >
78
+ Getting started
79
+ <span class="mint-onboarding-help__count">{{ state.tasksDone.value }}/{{ state.tasks.value.length }}</span>
80
+ <span class="mint-onboarding-help__bar" aria-hidden="true"><span :style="{ width: `${progress}%` }" /></span>
81
+ </button>
82
+ <button
83
+ ref="helpButton"
84
+ type="button"
85
+ class="mint-topbar__settings-btn"
86
+ :class="{ 'mint-topbar__settings-btn--open': open }"
87
+ data-onboarding="topbar-help"
88
+ :aria-label="state.unseenNews.value ? `Help, ${state.unseenNews.value} new` : 'Help'"
89
+ :aria-expanded="open"
90
+ @click="toggle"
91
+ >
92
+ <CircleHelp class="mint-topbar__settings-icon" aria-hidden="true" />
93
+ <span v-if="state.unseenNews.value" class="mint-onboarding-help__badge" aria-hidden="true">{{ state.unseenNews.value }}</span>
94
+ </button>
95
+
96
+ <Teleport to="body">
97
+ <div
98
+ v-if="open"
99
+ ref="panel"
100
+ class="mint-popover mint-onboarding-help__panel"
101
+ role="dialog"
102
+ aria-label="Help"
103
+ :style="{ left: `${panelPosition.x}px`, top: `${panelPosition.y}px` }"
104
+ >
105
+ <template v-if="state.tasks.value.length">
106
+ <div class="mint-onboarding-help__header">
107
+ <span class="mint-onboarding-help__heading">Getting started</span>
108
+ <span class="mint-onboarding-help__count">{{ state.tasksDone.value }}/{{ state.tasks.value.length }}</span>
109
+ </div>
110
+ <div class="mint-onboarding-help__progress" aria-hidden="true"><span :style="{ width: `${progress}%` }" /></div>
111
+ <ul class="mint-onboarding-help__todos">
112
+ <li
113
+ v-for="task in state.tasks.value"
114
+ :key="task.id"
115
+ :class="['mint-onboarding-help__todo', { 'mint-onboarding-help__todo--done': task.done }]"
116
+ >
117
+ <span class="mint-onboarding-help__check" role="img" :aria-label="task.done ? 'Done' : 'Not done'">
118
+ <Check v-if="task.done" :size="10" :stroke-width="3" aria-hidden="true" />
119
+ </span>
120
+ <span class="mint-onboarding-help__todo-title">{{ task.title }}</span>
121
+ <button
122
+ v-if="task.action && !task.done"
123
+ type="button"
124
+ class="mint-onboarding-help__link"
125
+ @click="run(task.action.run)"
126
+ >
127
+ {{ task.action.label }}
128
+ </button>
129
+ </li>
130
+ </ul>
131
+ <button
132
+ v-if="state.tasksDone.value < state.tasks.value.length"
133
+ type="button"
134
+ class="mint-onboarding-help__link mint-onboarding-help__hide"
135
+ @click="state.setChecklistHidden(!state.checklistHidden.value)"
136
+ >
137
+ {{ state.checklistHidden.value ? 'Show checklist in the top bar' : 'Hide checklist from the top bar' }}
138
+ </button>
139
+ </template>
140
+
141
+ <template v-if="state.tours.value.length">
142
+ <div class="mint-onboarding-help__section">Tours</div>
143
+ <button
144
+ v-for="tour in state.tours.value"
145
+ :key="tour.id"
146
+ type="button"
147
+ class="mint-menu-item"
148
+ @click="run(() => state.startTour(tour.id))"
149
+ >
150
+ <Play class="mint-onboarding-help__icon" aria-hidden="true" />
151
+ {{ tour.title }}
152
+ </button>
153
+ </template>
154
+
155
+ <template v-if="state.news.value.length">
156
+ <div class="mint-onboarding-help__section">What's new</div>
157
+ <button
158
+ v-for="item in state.news.value"
159
+ :key="item.id"
160
+ type="button"
161
+ class="mint-menu-item"
162
+ @click="run(() => state.showNews(item.id))"
163
+ >
164
+ {{ item.title }}
165
+ <span v-if="item.unseen" class="mint-onboarding-help__new">New</span>
166
+ </button>
167
+ </template>
168
+ </div>
169
+ </Teleport>
170
+ </div>
171
+ <OnboardingLayerInternal :state="state" />
172
+ </template>
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
- /** Dropdown trigger that switches between named pages, closing on outside click or Escape. */
3
- import { computed, useTemplateRef } from 'vue'
2
+ /** "Plugin / page" switcher: the plugin name, then a trigger that opens the page menu (hints, number keys 1-9). */
3
+ import { computed, nextTick, useTemplateRef } from 'vue'
4
4
  import { useDropdownState } from '../../composables/useDropdownState'
5
+ import { useMenuKeyboard } from '../../composables/useMenuKeyboard'
5
6
  import type { PageSelectorItem, PageSelectorItemInput } from '../../types/components'
6
7
  import { normalizeItemInput } from '../../utils/items'
7
8
  import { isPluginIconFormat } from '../../utils/pluginIcon'
@@ -13,27 +14,74 @@ interface Props {
13
14
  currentPageId?: string
14
15
  placeholder?: string
15
16
  minWidth?: string
17
+ /** Plugin name shown before the trigger and in the menu title. */
18
+ pluginName?: string
16
19
  }
17
20
 
18
21
  const props = withDefaults(defineProps<Props>(), {
19
22
  placeholder: 'Go to…',
20
- minWidth: '11.25rem',
21
23
  })
22
24
 
23
25
  const emit = defineEmits<{
24
26
  select: [page: PageSelectorItem]
25
27
  }>()
26
28
 
27
- const { isOpen, close, toggle } = useDropdownState({
29
+ const triggerEl = useTemplateRef<HTMLElement>('trigger')
30
+ const menuEl = useTemplateRef<HTMLElement>('menu')
31
+ const { isOpen, open, close, toggle } = useDropdownState({
28
32
  rootRef: useTemplateRef<HTMLElement>('root'),
29
- triggerRef: useTemplateRef<HTMLElement>('trigger'),
33
+ triggerRef: triggerEl,
30
34
  })
31
35
  const normalizedPages = computed<PageSelectorItem[]>(() => props.pages.map(normalizeItemInput))
36
+ const menuTitle = computed(() => (props.pluginName ? `${props.pluginName} pages` : 'Pages'))
32
37
 
33
38
  const current = computed<PageSelectorItem | undefined>(() =>
34
39
  normalizedPages.value.find((p) => p.id === props.currentPageId) ?? normalizedPages.value[0],
35
40
  )
36
41
 
42
+ const rows = () => Array.from(menuEl.value?.querySelectorAll<HTMLElement>('[role="menuitem"]') ?? [])
43
+ const isEnabled = (row: HTMLElement) => !row.hasAttribute('disabled') && row.getAttribute('aria-disabled') !== 'true'
44
+ const enabledRows = () => rows().filter(isEnabled)
45
+
46
+ const { handlePanelKeydown } = useMenuKeyboard({
47
+ items: enabledRows,
48
+ isOpen: () => isOpen.value,
49
+ open,
50
+ close,
51
+ })
52
+
53
+ function handleTriggerKeydown(event: KeyboardEvent) {
54
+ if (isOpen.value || !['ArrowDown', 'Enter', ' '].includes(event.key)) return
55
+ event.preventDefault()
56
+ open()
57
+ void nextTick(() => {
58
+ const items = enabledRows()
59
+ ;(items.find((row) => row.getAttribute('aria-current') === 'page') ?? items[0])?.focus()
60
+ })
61
+ }
62
+
63
+ // A click routes through ActionItemInternal, so links, router links and disabled rows act as on a pointer click.
64
+ function activate(row: HTMLElement) {
65
+ row.click()
66
+ if (!isOpen.value) triggerEl.value?.focus()
67
+ }
68
+
69
+ function handleMenuKeydown(event: KeyboardEvent) {
70
+ if (/^[1-9]$/.test(event.key)) {
71
+ event.preventDefault()
72
+ const row = rows()[Number(event.key) - 1]
73
+ if (row && isEnabled(row)) activate(row)
74
+ return
75
+ }
76
+ const focused = document.activeElement as HTMLElement | null
77
+ if ((event.key === 'Enter' || event.key === ' ') && focused && enabledRows().includes(focused)) {
78
+ event.preventDefault()
79
+ activate(focused)
80
+ return
81
+ }
82
+ handlePanelKeydown(event)
83
+ }
84
+
37
85
  function handleSelect(page: PageSelectorItem) {
38
86
  if (page.disabled) return
39
87
  if (page.href || page.to) {
@@ -46,82 +94,102 @@ function handleSelect(page: PageSelectorItem) {
46
94
  </script>
47
95
 
48
96
  <template>
49
- <div ref="root" class="mint-page-selector" :style="{ minWidth }">
50
- <button
51
- ref="trigger"
52
- type="button"
53
- class="mint-page-selector__trigger"
54
- :class="{ 'mint-page-selector__trigger--open': isOpen }"
55
- :aria-expanded="isOpen"
56
- aria-haspopup="menu"
57
- @click.stop="toggle"
58
- >
59
- <span
60
- v-if="current"
61
- class="mint-page-selector__icon"
62
- :class="{ 'mint-page-selector__icon--metadata': isPluginIconFormat(current.icon) }"
63
- >
64
- <slot name="icon" :page="current">
65
- <PluginIcon
66
- v-if="isPluginIconFormat(current.icon)"
67
- class="mint-page-selector__trigger-metadata-icon"
68
- :icon="current.icon"
69
- size="sm"
70
- variant="solid"
71
- />
72
- <span v-else class="mint-page-selector__icon-fallback">{{ current.label.charAt(0) }}</span>
73
- </slot>
74
- </span>
75
- <span class="mint-page-selector__label">{{ current?.label ?? placeholder }}</span>
76
- <span class="mint-page-selector__chevron-wrap" aria-hidden="true">
77
- <svg
78
- class="mint-page-selector__chevron"
79
- :class="{ 'mint-page-selector__chevron--open': isOpen }"
80
- viewBox="0 0 24 24"
81
- fill="none"
82
- stroke="currentColor"
83
- stroke-width="2"
84
- stroke-linecap="round"
85
- stroke-linejoin="round"
86
- >
87
- <path d="m6 9 6 6 6-6" />
88
- </svg>
89
- </span>
90
- </button>
91
-
92
- <div v-if="isOpen" class="mint-page-selector__menu" role="menu">
93
- <div class="mint-page-selector__menu-title">Go to</div>
94
- <ActionItemInternal
95
- v-for="page in normalizedPages"
96
- :key="page.id"
97
- :href="page.href"
98
- :to="page.to"
99
- :disabled="page.disabled"
100
- :class="[
101
- 'mint-page-selector__item',
102
- { 'mint-page-selector__item--active': page.id === current?.id },
103
- { 'mint-page-selector__item--disabled': page.disabled },
104
- ]"
105
- role="menuitem"
106
- @click="handleSelect(page)"
97
+ <div ref="root" class="mint-page-selector__crumb">
98
+ <template v-if="pluginName">
99
+ <span class="mint-page-selector__plugin">{{ pluginName }}</span>
100
+ <span class="mint-page-selector__slash" aria-hidden="true">/</span>
101
+ </template>
102
+ <div class="mint-page-selector" :style="minWidth ? { minWidth } : undefined">
103
+ <button
104
+ ref="trigger"
105
+ type="button"
106
+ class="mint-page-selector__trigger"
107
+ :class="{ 'mint-page-selector__trigger--open': isOpen }"
108
+ :aria-expanded="isOpen"
109
+ aria-haspopup="menu"
110
+ @click.stop="toggle"
111
+ @keydown="handleTriggerKeydown"
107
112
  >
108
113
  <span
109
- class="mint-page-selector__item-icon"
110
- :class="{ 'mint-page-selector__item-icon--metadata': isPluginIconFormat(page.icon) }"
114
+ v-if="current"
115
+ class="mint-page-selector__icon"
116
+ :class="{ 'mint-page-selector__icon--metadata': isPluginIconFormat(current.icon) }"
111
117
  >
112
- <slot name="item-icon" :page="page">
118
+ <slot name="icon" :page="current">
113
119
  <PluginIcon
114
- v-if="isPluginIconFormat(page.icon)"
115
- class="mint-page-selector__metadata-icon"
116
- :icon="page.icon"
120
+ v-if="isPluginIconFormat(current.icon)"
121
+ class="mint-page-selector__trigger-metadata-icon"
122
+ :icon="current.icon"
117
123
  size="sm"
118
124
  variant="solid"
119
125
  />
120
- <span v-else class="mint-page-selector__icon-fallback">{{ page.label.charAt(0) }}</span>
126
+ <span v-else class="mint-page-selector__icon-fallback">{{ current.label.charAt(0) }}</span>
121
127
  </slot>
122
128
  </span>
123
- <span class="mint-page-selector__item-label">{{ page.label }}</span>
124
- </ActionItemInternal>
129
+ <span class="mint-page-selector__label">{{ current?.label ?? placeholder }}</span>
130
+ <span class="mint-page-selector__chevron-wrap" aria-hidden="true">
131
+ <svg
132
+ class="mint-page-selector__chevron"
133
+ :class="{ 'mint-page-selector__chevron--open': isOpen }"
134
+ viewBox="0 0 24 24"
135
+ fill="none"
136
+ stroke="currentColor"
137
+ stroke-width="2"
138
+ stroke-linecap="round"
139
+ stroke-linejoin="round"
140
+ >
141
+ <path d="m6 9 6 6 6-6" />
142
+ </svg>
143
+ </span>
144
+ </button>
145
+
146
+ <div
147
+ v-if="isOpen"
148
+ ref="menu"
149
+ class="mint-page-selector__menu"
150
+ role="menu"
151
+ :aria-label="menuTitle"
152
+ @keydown="handleMenuKeydown"
153
+ >
154
+ <div class="mint-page-selector__menu-title" aria-hidden="true">{{ menuTitle }}</div>
155
+ <ActionItemInternal
156
+ v-for="(page, index) in normalizedPages"
157
+ :key="page.id"
158
+ :href="page.href"
159
+ :to="page.to"
160
+ :disabled="page.disabled"
161
+ :class="[
162
+ 'mint-page-selector__item',
163
+ { 'mint-page-selector__item--active': page.id === current?.id },
164
+ { 'mint-page-selector__item--disabled': page.disabled },
165
+ ]"
166
+ role="menuitem"
167
+ :aria-current="page.id === current?.id ? 'page' : undefined"
168
+ :aria-keyshortcuts="index < 9 ? String(index + 1) : undefined"
169
+ @click="handleSelect(page)"
170
+ >
171
+ <span
172
+ class="mint-page-selector__item-icon"
173
+ :class="{ 'mint-page-selector__item-icon--metadata': isPluginIconFormat(page.icon) }"
174
+ >
175
+ <slot name="item-icon" :page="page">
176
+ <PluginIcon
177
+ v-if="isPluginIconFormat(page.icon)"
178
+ class="mint-page-selector__metadata-icon"
179
+ :icon="page.icon"
180
+ size="sm"
181
+ variant="solid"
182
+ />
183
+ <span v-else class="mint-page-selector__icon-fallback">{{ page.label.charAt(0) }}</span>
184
+ </slot>
185
+ </span>
186
+ <span class="mint-page-selector__item-text">
187
+ <span class="mint-page-selector__item-label">{{ page.label }}</span>
188
+ <span v-if="page.hint" class="mint-page-selector__item-hint">{{ page.hint }}</span>
189
+ </span>
190
+ <span v-if="index < 9" class="mint-page-selector__item-key" aria-hidden="true">{{ index + 1 }}</span>
191
+ </ActionItemInternal>
192
+ </div>
125
193
  </div>
126
194
  </div>
127
195
  </template>
@@ -21,7 +21,26 @@ const emit = defineEmits<{
21
21
  const openItemId = ref<string | null>(null)
22
22
  const itemRefs = ref<Map<string, HTMLElement>>(new Map())
23
23
 
24
- const normalizedItems = computed<PillNavItem[]>(() => props.items.map(normalizePillNavItem))
24
+ /** The bar shows this many items; the rest fold into a More dropdown. */
25
+ const MAX_VISIBLE_ITEMS = 4
26
+ const MORE_ITEM_ID = '__mint-pill-nav-more'
27
+
28
+ const allItems = computed<PillNavItem[]>(() => props.items.map(normalizePillNavItem))
29
+
30
+ const normalizedItems = computed<PillNavItem[]>(() => {
31
+ if (allItems.value.length <= MAX_VISIBLE_ITEMS) return allItems.value
32
+ const folded = allItems.value.slice(MAX_VISIBLE_ITEMS)
33
+ return [
34
+ ...allItems.value.slice(0, MAX_VISIBLE_ITEMS),
35
+ {
36
+ id: MORE_ITEM_ID,
37
+ label: 'More',
38
+ dot: folded.some(item => item.dot),
39
+ // A folded group contributes its options; a plain item becomes one option.
40
+ children: folded.flatMap(item => item.children?.length ? item.children : [item]),
41
+ },
42
+ ]
43
+ })
25
44
 
26
45
  function handleClick(item: PillNavItem) {
27
46
  if (item.disabled) return
@@ -35,8 +54,20 @@ function handleClick(item: PillNavItem) {
35
54
 
36
55
  function handleOptionClick(option: PillNavOption, item: PillNavItem) {
37
56
  if (option.disabled) return
38
- if (!option.href && !option.to) emit('option-select', option, item)
39
57
  openItemId.value = null
58
+ if (option.href || option.to) return
59
+ if (item.id !== MORE_ITEM_ID) {
60
+ emit('option-select', option, item)
61
+ return
62
+ }
63
+ // A folded entry reports as it would from its own pill.
64
+ const folded = allItems.value.find(candidate => candidate.id === option.id && !candidate.children?.length)
65
+ if (folded) {
66
+ emit('select', folded)
67
+ return
68
+ }
69
+ const group = allItems.value.find(candidate => candidate.children?.some(child => child.id === option.id))
70
+ if (group) emit('option-select', option, group)
40
71
  }
41
72
 
42
73
  function normalizePillNavItem(item: PillNavItemInput): PillNavItem {