@morscherlab/mint-sdk 1.2.0-beta.9 → 1.2.1

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 (215) hide show
  1. package/README.md +30 -0
  2. package/dist/{ExperimentPopover-oL0fg_A0.js → ExperimentPopover-CJdNlCZb.js} +21 -14
  3. package/dist/ExperimentPopover-CJdNlCZb.js.map +1 -0
  4. package/dist/{ExperimentSelectorModal-BeEOomw_.js → ExperimentSelectorModal-DHE9k8TP.js} +5 -5
  5. package/dist/{ExperimentSelectorModal-BeEOomw_.js.map → ExperimentSelectorModal-DHE9k8TP.js.map} +1 -1
  6. package/dist/{SettingsModal-Dphh3RrI.js → SettingsModal-DOcCf0Vo.js} +41 -18
  7. package/dist/SettingsModal-DOcCf0Vo.js.map +1 -0
  8. package/dist/{Skeleton-DxtwJ5gt.js → Skeleton-jhF9wUkU.js} +19 -9
  9. package/dist/Skeleton-jhF9wUkU.js.map +1 -0
  10. package/dist/__tests__/apiErrors.test.d.ts +1 -0
  11. package/dist/__tests__/components/FileBrowserModal.test.d.ts +1 -0
  12. package/dist/__tests__/components/FilePicker.state.test.d.ts +1 -0
  13. package/dist/__tests__/components/FilePicker.test.d.ts +1 -0
  14. package/dist/__tests__/components/FormField.test.d.ts +1 -0
  15. package/dist/__tests__/components/NumberInput.test.d.ts +1 -0
  16. package/dist/__tests__/components/PlotlyChart.test.d.ts +1 -0
  17. package/dist/__tests__/components/iconButtonIconSizing.test.d.ts +1 -0
  18. package/dist/__tests__/composables/localFiles.test.d.ts +1 -0
  19. package/dist/__tests__/composables/useFileBrowser.test.d.ts +1 -0
  20. package/dist/__tests__/composables/usePickerSelection.test.d.ts +1 -0
  21. package/dist/__tests__/composables/usePlatformFilePickerAdapter.test.d.ts +1 -0
  22. package/dist/__tests__/styles/tokenOnlyColors.test.d.ts +1 -0
  23. package/dist/apiErrors.d.ts +26 -0
  24. package/dist/components/AppSidebar.vue.d.ts +7 -7
  25. package/dist/components/BaseInput.vue.d.ts +1 -1
  26. package/dist/components/BasePill.vue.d.ts +3 -0
  27. package/dist/components/BaseSelect.vue.d.ts +1 -1
  28. package/dist/components/BaseTabs.vue.d.ts +1 -4
  29. package/dist/components/BaseTextarea.vue.d.ts +1 -1
  30. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
  31. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +2 -2
  32. package/dist/components/ConcentrationInput.vue.d.ts +1 -1
  33. package/dist/components/DatePicker.vue.d.ts +1 -1
  34. package/dist/components/DateTimePicker.vue.d.ts +1 -1
  35. package/dist/components/FileBrowserModal.vue.d.ts +99 -0
  36. package/dist/components/FilePicker.vue.d.ts +32 -0
  37. package/dist/components/FileUploader.vue.d.ts +8 -2
  38. package/dist/components/FormField.vue.d.ts +8 -1
  39. package/dist/components/FormulaInput.vue.d.ts +1 -1
  40. package/dist/components/GeneratedPlotlyResult.vue.d.ts +1 -3
  41. package/dist/components/MobileSupportGate.vue.d.ts +1 -1
  42. package/dist/components/MoleculeInput.vue.d.ts +1 -1
  43. package/dist/components/NumberInput.vue.d.ts +1 -1
  44. package/dist/components/PlotlyChart.vue.d.ts +59 -0
  45. package/dist/components/PluginWorkspaceView.props.d.ts +3 -3
  46. package/dist/components/ResourceCard.vue.d.ts +1 -1
  47. package/dist/components/SecretInput.vue.d.ts +1 -1
  48. package/dist/components/SequenceInput.vue.d.ts +1 -1
  49. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  50. package/dist/components/TagsInput.vue.d.ts +1 -1
  51. package/dist/components/TimePicker.vue.d.ts +1 -1
  52. package/dist/components/TimeRangeInput.vue.d.ts +1 -1
  53. package/dist/components/UnitInput.vue.d.ts +1 -1
  54. package/dist/components/WellPlate.vue.d.ts +1 -1
  55. package/dist/components/fileBrowserPresentation.d.ts +10 -0
  56. package/dist/components/index.d.ts +4 -0
  57. package/dist/components/index.js +8 -8
  58. package/dist/components/internal/FileBrowserDialogInternal.vue.d.ts +34 -0
  59. package/dist/components/internal/FileBrowserListingInternal.vue.d.ts +25 -0
  60. package/dist/components/internal/FileBrowserToolbarInternal.vue.d.ts +26 -0
  61. package/dist/components/internal/FilePickerPreviewInternal.vue.d.ts +18 -0
  62. package/dist/components/internal/FilePickerTreeInternal.vue.d.ts +31 -0
  63. package/dist/{components-CgAUzF8F.js → components-Cp8DcNrU.js} +4363 -1730
  64. package/dist/components-Cp8DcNrU.js.map +1 -0
  65. package/dist/composables/filePicker/localFiles.d.ts +3 -0
  66. package/dist/composables/filePicker/usePickerNavigation.d.ts +96 -0
  67. package/dist/composables/filePicker/usePickerSelection.d.ts +31 -0
  68. package/dist/composables/filePicker/validation.d.ts +5 -0
  69. package/dist/composables/index.d.ts +2 -0
  70. package/dist/composables/index.js +10 -10
  71. package/dist/composables/pluginWorkspaceModel.d.ts +0 -3
  72. package/dist/composables/useApi.d.ts +14 -7
  73. package/dist/composables/useFileBrowser.d.ts +55 -0
  74. package/dist/composables/useFilePicker.d.ts +189 -0
  75. package/dist/composables/usePlatformFilePickerAdapter.d.ts +11 -0
  76. package/dist/composables/useToast.d.ts +22 -1
  77. package/dist/{composables-B_pia3hO.js → composables-D_4qqOlp.js} +300 -11
  78. package/dist/composables-D_4qqOlp.js.map +1 -0
  79. package/dist/{errors-_W0-LbA6.js → errors-BLuqMee5.js} +78 -15
  80. package/dist/errors-BLuqMee5.js.map +1 -0
  81. package/dist/{experiment-Cwl9AYxw.js → experiment-CVDRAnqp.js} +2 -2
  82. package/dist/{experiment-Cwl9AYxw.js.map → experiment-CVDRAnqp.js.map} +1 -1
  83. package/dist/{experiment-utils-DV3DrvE_.js → experiment-utils-CV2Wza3z.js} +1 -1
  84. package/dist/{experiment-utils-DV3DrvE_.js.map → experiment-utils-CV2Wza3z.js.map} +1 -1
  85. package/dist/index.d.ts +1 -0
  86. package/dist/index.js +15 -15
  87. package/dist/install.js +2 -2
  88. package/dist/stores/index.js +2 -2
  89. package/dist/styles.css +2957 -1708
  90. package/dist/templates/index.js +2 -2
  91. package/dist/{templates-Qq113pyZ.js → templates-Dd9bBSs6.js} +2 -2
  92. package/dist/{templates-Qq113pyZ.js.map → templates-Dd9bBSs6.js.map} +1 -1
  93. package/dist/types/components.d.ts +21 -0
  94. package/dist/types/fileBrowserTypes.d.ts +77 -0
  95. package/dist/types/filePicker.d.ts +77 -0
  96. package/dist/types/form-builder.d.ts +2 -0
  97. package/dist/types/generated-ui.d.ts +2 -0
  98. package/dist/types/index.d.ts +3 -1
  99. package/dist/{useControlSchema-aMZlb4Sc.js → useControlSchema-BB2GRPhW.js} +190 -105
  100. package/dist/useControlSchema-BB2GRPhW.js.map +1 -0
  101. package/dist/{useDropdownState-DitCtrTT.js → useDropdownState-C7hRiVQf.js} +2 -2
  102. package/dist/{useDropdownState-DitCtrTT.js.map → useDropdownState-C7hRiVQf.js.map} +1 -1
  103. package/dist/{useExperimentSelector-I8HC8hUu.js → useExperimentSelector-G_CAOFTX.js} +4 -4
  104. package/dist/{useExperimentSelector-I8HC8hUu.js.map → useExperimentSelector-G_CAOFTX.js.map} +1 -1
  105. package/dist/{useFormBuilder-TCxNnqeb.js → useFormBuilder-BnGSdSRG.js} +2 -2
  106. package/dist/{useFormBuilder-TCxNnqeb.js.map → useFormBuilder-BnGSdSRG.js.map} +1 -1
  107. package/dist/{useProtocolTemplates-BmX_iVBR.js → validation-CTxuzLIX.js} +70 -13
  108. package/dist/validation-CTxuzLIX.js.map +1 -0
  109. package/package.json +1 -1
  110. package/src/__tests__/apiErrors.test.ts +81 -0
  111. package/src/__tests__/components/AppSidebar.test.ts +84 -94
  112. package/src/__tests__/components/AppToastContainer.test.ts +55 -1
  113. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +0 -21
  114. package/src/__tests__/components/ControlWorkspaceView.test.ts +0 -2
  115. package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +0 -2
  116. package/src/__tests__/components/ExperimentPopover.test.ts +30 -0
  117. package/src/__tests__/components/FileBrowserModal.test.ts +460 -0
  118. package/src/__tests__/components/FilePicker.state.test.ts +287 -0
  119. package/src/__tests__/components/FilePicker.test.ts +487 -0
  120. package/src/__tests__/components/FormBuilder.test.ts +26 -0
  121. package/src/__tests__/components/FormField.test.ts +26 -0
  122. package/src/__tests__/components/GeneratedPathInput.test.ts +18 -0
  123. package/src/__tests__/components/GeneratedPlotlyResult.test.ts +52 -5
  124. package/src/__tests__/components/NumberInput.test.ts +105 -0
  125. package/src/__tests__/components/PlotlyChart.test.ts +305 -0
  126. package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +1 -2
  127. package/src/__tests__/components/PluginWorkspaceView.test.ts +1 -2
  128. package/src/__tests__/components/SegmentedControl.test.ts +19 -0
  129. package/src/__tests__/components/iconButtonIconSizing.test.ts +18 -0
  130. package/src/__tests__/composables/localFiles.test.ts +57 -0
  131. package/src/__tests__/composables/useApi.test.ts +92 -9
  132. package/src/__tests__/composables/useFileBrowser.test.ts +305 -0
  133. package/src/__tests__/composables/useGeneratedAnalysis.test.ts +7 -2
  134. package/src/__tests__/composables/usePickerSelection.test.ts +98 -0
  135. package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +67 -0
  136. package/src/__tests__/composables/usePluginClient.test.ts +3 -0
  137. package/src/__tests__/generatedUi.test.ts +111 -0
  138. package/src/__tests__/styles/tokenOnlyColors.test.ts +16 -0
  139. package/src/apiErrors.ts +85 -0
  140. package/src/components/AppLayout.story.vue +1 -1
  141. package/src/components/AppSidebar.story.vue +7 -10
  142. package/src/components/AppSidebar.vue +178 -58
  143. package/src/components/AppToastContainer.vue +65 -23
  144. package/src/components/BasePill.story.vue +2 -0
  145. package/src/components/BasePill.vue +4 -0
  146. package/src/components/BaseTabs.story.vue +0 -42
  147. package/src/components/BaseTabs.vue +3 -6
  148. package/src/components/ExperimentPopover.vue +10 -2
  149. package/src/components/FileBrowserModal.story.vue +203 -0
  150. package/src/components/FileBrowserModal.vue +384 -0
  151. package/src/components/FilePicker.story.vue +44 -0
  152. package/src/components/FilePicker.vue +284 -0
  153. package/src/components/FileUploader.vue +42 -15
  154. package/src/components/FormField.vue +10 -3
  155. package/src/components/GeneratedPathInput.vue +11 -7
  156. package/src/components/GeneratedPlotlyResult.vue +34 -69
  157. package/src/components/GeneratedResultRenderer.vue +0 -5
  158. package/src/components/NumberInput.story.vue +32 -13
  159. package/src/components/NumberInput.vue +116 -55
  160. package/src/components/PlotlyChart.story.vue +61 -0
  161. package/src/components/PlotlyChart.vue +233 -0
  162. package/src/components/PluginWorkspaceView.props.ts +3 -3
  163. package/src/components/SegmentedControl.vue +2 -2
  164. package/src/components/fileBrowserPresentation.ts +39 -0
  165. package/src/components/index.ts +5 -0
  166. package/src/components/internal/FileBrowserDialogInternal.vue +75 -0
  167. package/src/components/internal/FileBrowserListingInternal.vue +197 -0
  168. package/src/components/internal/FileBrowserToolbarInternal.vue +136 -0
  169. package/src/components/internal/FilePickerPreviewInternal.vue +100 -0
  170. package/src/components/internal/FilePickerTreeInternal.vue +238 -0
  171. package/src/components/internal/FormFieldRendererInternal.vue +17 -0
  172. package/src/components/internal/WorkspaceSidebarInternal.vue +1 -2
  173. package/src/composables/filePicker/localFiles.ts +65 -0
  174. package/src/composables/filePicker/usePickerNavigation.ts +220 -0
  175. package/src/composables/filePicker/usePickerSelection.ts +270 -0
  176. package/src/composables/filePicker/validation.ts +41 -0
  177. package/src/composables/index.ts +7 -0
  178. package/src/composables/pluginWorkspaceModel.ts +0 -3
  179. package/src/composables/useApi.ts +57 -13
  180. package/src/composables/useFileBrowser.ts +314 -0
  181. package/src/composables/useFilePicker.ts +366 -0
  182. package/src/composables/useGeneratedAnalysis.ts +10 -17
  183. package/src/composables/usePlatformFilePickerAdapter.ts +104 -0
  184. package/src/composables/usePluginClient.ts +3 -0
  185. package/src/composables/usePluginWorkspace.ts +1 -2
  186. package/src/composables/useToast.ts +21 -5
  187. package/src/generated/schema.ts +58 -18
  188. package/src/index.ts +3 -0
  189. package/src/styles/components/app-sidebar.css +282 -121
  190. package/src/styles/components/file-browser-modal.css +489 -0
  191. package/src/styles/components/file-picker.css +502 -0
  192. package/src/styles/components/form-builder.css +1 -1
  193. package/src/styles/components/form-field.css +28 -0
  194. package/src/styles/components/icon-button.css +9 -0
  195. package/src/styles/components/number-input.css +73 -91
  196. package/src/styles/components/pill.css +49 -51
  197. package/src/styles/components/segmented-control.css +16 -11
  198. package/src/styles/components/tabs.css +18 -34
  199. package/src/styles/components/toast.css +136 -27
  200. package/src/styles/index.css +3 -0
  201. package/src/styles/variables.css +25 -5
  202. package/src/types/components.ts +23 -0
  203. package/src/types/fileBrowserTypes.ts +85 -0
  204. package/src/types/filePicker.ts +72 -0
  205. package/src/types/form-builder.ts +2 -0
  206. package/src/types/generated-ui.ts +2 -0
  207. package/src/types/index.ts +16 -0
  208. package/dist/ExperimentPopover-oL0fg_A0.js.map +0 -1
  209. package/dist/SettingsModal-Dphh3RrI.js.map +0 -1
  210. package/dist/Skeleton-DxtwJ5gt.js.map +0 -1
  211. package/dist/components-CgAUzF8F.js.map +0 -1
  212. package/dist/composables-B_pia3hO.js.map +0 -1
  213. package/dist/errors-_W0-LbA6.js.map +0 -1
  214. package/dist/useControlSchema-aMZlb4Sc.js.map +0 -1
  215. package/dist/useProtocolTemplates-BmX_iVBR.js.map +0 -1
@@ -1,17 +1,25 @@
1
1
  <script setup lang="ts">
2
- /** Preferred toast container driven by useToast, with auto-dismiss progress bar and click-to-close on each notification. */
2
+ /** Preferred toast container driven by useToast: title/detail, up to two actions, TTL or indeterminate bar, click-to-close. */
3
3
  import { useToast } from '../composables/useToast'
4
- import type { Toast } from '../types'
4
+ import type { Toast, ToastAction } from '../types'
5
5
 
6
6
  const { toasts, dismiss } = useToast()
7
7
 
8
- // Semantic accents come from the shared tokens rather than a fourth copy of
9
- // the same four hexes.
10
- const accentColors: Record<Toast['type'], string> = {
11
- success: 'var(--mint-success)',
12
- error: 'var(--mint-error)',
13
- warning: 'var(--mint-warning)',
14
- info: 'var(--mint-info)',
8
+ function title(toast: Toast): string {
9
+ return toast.title ?? toast.message
10
+ }
11
+
12
+ function actions(toast: Toast): ToastAction[] {
13
+ return (toast.actions ?? []).slice(0, 2)
14
+ }
15
+
16
+ function progress(toast: Toast): NonNullable<Toast['progress']> {
17
+ return toast.progress ?? 'ttl'
18
+ }
19
+
20
+ function runAction(toast: Toast, action: ToastAction) {
21
+ action.onClick()
22
+ dismiss(toast.id)
15
23
  }
16
24
  </script>
17
25
 
@@ -22,36 +30,70 @@ const accentColors: Record<Toast['type'], string> = {
22
30
  <div
23
31
  v-for="toast in toasts"
24
32
  :key="toast.id"
25
- :class="['mint-toast__item', `mint-toast__item--${toast.type}`]"
26
- :style="{
27
- borderLeftColor: accentColors[toast.type],
28
- }"
29
- role="alert"
33
+ :class="[
34
+ 'mint-toast__item',
35
+ `mint-toast__item--${toast.type}`,
36
+ (toast.count ?? 1) > 1 ? 'mint-toast__item--stacked' : '',
37
+ ]"
38
+ :role="toast.type === 'error' || toast.type === 'warning' ? 'alert' : 'status'"
30
39
  @click="dismiss(toast.id)"
31
40
  >
32
41
  <!-- Lucide circle-check -->
33
- <svg v-if="toast.type === 'success'" class="mint-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" :style="{ color: accentColors[toast.type] }">
42
+ <svg v-if="toast.type === 'success'" class="mint-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
34
43
  <circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" />
35
44
  </svg>
36
45
  <!-- Lucide circle-x -->
37
- <svg v-else-if="toast.type === 'error'" class="mint-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" :style="{ color: accentColors[toast.type] }">
46
+ <svg v-else-if="toast.type === 'error'" class="mint-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
38
47
  <circle cx="12" cy="12" r="10" /><path d="m15 9-6 6" /><path d="m9 9 6 6" />
39
48
  </svg>
40
49
  <!-- Lucide triangle-alert -->
41
- <svg v-else-if="toast.type === 'warning'" class="mint-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" :style="{ color: accentColors[toast.type] }">
50
+ <svg v-else-if="toast.type === 'warning'" class="mint-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
42
51
  <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /><path d="M12 9v4" /><path d="M12 17h.01" />
43
52
  </svg>
44
53
  <!-- Lucide info -->
45
- <svg v-else class="mint-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" :style="{ color: accentColors[toast.type] }">
54
+ <svg v-else class="mint-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
46
55
  <circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" />
47
56
  </svg>
48
- <span class="mint-toast__message">{{ toast.message }}</span>
57
+
58
+ <div class="mint-toast__body">
59
+ <div class="mint-toast__title">
60
+ <span class="mint-toast__message">{{ title(toast) }}</span>
61
+ <span v-if="(toast.count ?? 1) > 1" class="mint-toast__count">×{{ toast.count }}</span>
62
+ </div>
63
+ <div v-if="toast.detail" class="mint-toast__detail">{{ toast.detail }}</div>
64
+ <div v-if="actions(toast).length" class="mint-toast__actions">
65
+ <button
66
+ v-for="action in actions(toast)"
67
+ :key="action.label"
68
+ type="button"
69
+ :class="['mint-toast__action', action.primary ? 'mint-toast__action--primary' : '']"
70
+ @click.stop="runAction(toast, action)"
71
+ >
72
+ {{ action.label }}
73
+ </button>
74
+ </div>
75
+ </div>
76
+
77
+ <button
78
+ v-if="toast.dismissible !== false"
79
+ type="button"
80
+ class="mint-toast__close"
81
+ aria-label="Dismiss"
82
+ @click.stop="dismiss(toast.id)"
83
+ >
84
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true">
85
+ <path d="M18 6 6 18" /><path d="m6 6 12 12" />
86
+ </svg>
87
+ </button>
88
+
49
89
  <span
90
+ v-if="progress(toast) === 'ttl'"
50
91
  class="mint-toast__progress"
51
- :style="{
52
- backgroundColor: accentColors[toast.type],
53
- animationDuration: `${toast.duration ?? 3500}ms`,
54
- }"
92
+ :style="{ animationDuration: `${toast.duration ?? 3500}ms` }"
93
+ />
94
+ <span
95
+ v-else-if="progress(toast) === 'indeterminate'"
96
+ class="mint-toast__progress mint-toast__progress--indeterminate"
55
97
  />
56
98
  </div>
57
99
  </TransitionGroup>
@@ -16,6 +16,7 @@ const sizes: PillSize[] = ['sm', 'md', 'lg']
16
16
  :size="state.size"
17
17
  :removable="state.removable"
18
18
  :disabled="state.disabled"
19
+ :dot="state.dot"
19
20
  @remove="() => console.log('Remove clicked')"
20
21
  >
21
22
  {{ state.label }}
@@ -37,6 +38,7 @@ const sizes: PillSize[] = ['sm', 'md', 'lg']
37
38
  />
38
39
  <HstCheckbox v-model="state.removable" title="Removable" />
39
40
  <HstCheckbox v-model="state.disabled" title="Disabled" />
41
+ <HstCheckbox v-model="state.dot" title="Status dot" />
40
42
  </template>
41
43
  </Variant>
42
44
 
@@ -28,6 +28,8 @@ interface Props {
28
28
  disabled?: boolean
29
29
  /** Show icon slot */
30
30
  icon?: boolean
31
+ /** Leading status dot in the pill's tone color */
32
+ dot?: boolean
31
33
  }
32
34
 
33
35
  const props = withDefaults(defineProps<Props>(), {
@@ -37,6 +39,7 @@ const props = withDefaults(defineProps<Props>(), {
37
39
  removable: false,
38
40
  disabled: false,
39
41
  icon: false,
42
+ dot: false,
40
43
  })
41
44
 
42
45
  /**
@@ -64,6 +67,7 @@ function handleRemove(event: MouseEvent) {
64
67
  { 'mint-pill--disabled': disabled, 'mint-pill--with-icon': icon },
65
68
  ]"
66
69
  >
70
+ <span v-if="dot" class="mint-pill__dot" aria-hidden="true" />
67
71
  <span v-if="icon" class="mint-pill__icon">
68
72
  <slot name="icon" />
69
73
  </span>
@@ -1,9 +1,7 @@
1
1
  <script setup lang="ts">
2
- import { ref } from 'vue'
3
2
  import BaseTabs from './BaseTabs.vue'
4
3
  import type { TabItem } from '../types'
5
4
 
6
- const activeTab = ref('overview')
7
5
 
8
6
  const basicTabs: TabItem[] = [
9
7
  { id: 'overview', label: 'Overview' },
@@ -25,12 +23,9 @@ const tabsWithIcons: TabItem[] = [
25
23
  { id: 'analysis', label: 'Analysis', icon: '\u{1F52C}' },
26
24
  ]
27
25
 
28
- const variants = ['underline', 'pills'] as const
29
-
30
26
  function initPlayground() {
31
27
  return {
32
28
  activeTab: 'overview',
33
- variant: 'underline' as const,
34
29
  }
35
30
  }
36
31
  </script>
@@ -43,7 +38,6 @@ function initPlayground() {
43
38
  <BaseTabs
44
39
  v-model="state.activeTab"
45
40
  :tabs="basicTabs"
46
- :variant="state.variant"
47
41
  />
48
42
  <p style="margin-top: 1rem; font-size: 0.875rem; color: var(--text-muted, #94a3b8);">
49
43
  Active tab: <code>{{ state.activeTab }}</code>
@@ -52,11 +46,6 @@ function initPlayground() {
52
46
  </template>
53
47
 
54
48
  <template #controls="{ state }">
55
- <HstSelect
56
- v-model="state.variant"
57
- title="Variant"
58
- :options="variants.map(v => ({ label: v, value: v }))"
59
- />
60
49
  <HstSelect
61
50
  v-model="state.activeTab"
62
51
  title="Active Tab"
@@ -65,27 +54,11 @@ function initPlayground() {
65
54
  </template>
66
55
  </Variant>
67
56
 
68
- <Variant title="All Variants">
69
- <div style="padding: 2rem; display: flex; flex-direction: column; gap: 2rem;">
70
- <div v-for="variant in variants" :key="variant">
71
- <p style="margin: 0 0 0.5rem; font-size: 0.75rem; color: var(--text-muted, #94a3b8); text-transform: uppercase; letter-spacing: 0.05em;">
72
- {{ variant }}
73
- </p>
74
- <BaseTabs
75
- v-model="activeTab"
76
- :tabs="basicTabs"
77
- :variant="variant"
78
- />
79
- </div>
80
- </div>
81
- </Variant>
82
-
83
57
  <Variant title="With Badges">
84
58
  <div style="padding: 2rem;">
85
59
  <BaseTabs
86
60
  model-value="inbox"
87
61
  :tabs="tabsWithBadges"
88
- variant="underline"
89
62
  />
90
63
  </div>
91
64
  </Variant>
@@ -95,7 +68,6 @@ function initPlayground() {
95
68
  <BaseTabs
96
69
  model-value="plate"
97
70
  :tabs="tabsWithIcons"
98
- variant="pills"
99
71
  />
100
72
  </div>
101
73
  </Variant>
@@ -105,23 +77,9 @@ function initPlayground() {
105
77
  <BaseTabs
106
78
  model-value="inbox"
107
79
  :tabs="tabsWithBadges"
108
- variant="underline"
109
80
  />
110
81
  </div>
111
82
  </Variant>
112
83
 
113
- <Variant title="Pills — matches SegmentedControl.card">
114
- <div style="padding: 2rem; display: flex; justify-content: center;">
115
- <BaseTabs
116
- v-model="activeTab"
117
- variant="pills"
118
- :tabs="[
119
- { id: 'overview', label: 'Overview' },
120
- { id: 'samples', label: 'Samples' },
121
- { id: 'results', label: 'Results' },
122
- ]"
123
- />
124
- </div>
125
- </Variant>
126
84
  </Story>
127
85
  </template>
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- /** Renders a tab bar with underline or pills variant, badges, icons, and disabled tab support. */
2
+ /** Renders an underline tab bar with mono count badges, icons, and disabled tab support. */
3
3
  import { computed, type ComponentPublicInstance } from 'vue'
4
4
  import type { TabItem, TabItemInput } from '../types'
5
5
  import { normalizeItemInput } from '../utils/items'
@@ -8,12 +8,9 @@ import { useRovingFocus } from '../composables/useRovingFocus'
8
8
  interface Props {
9
9
  modelValue: string
10
10
  tabs: TabItemInput[]
11
- variant?: 'underline' | 'pills'
12
11
  }
13
12
 
14
- const props = withDefaults(defineProps<Props>(), {
15
- variant: 'underline',
16
- })
13
+ const props = defineProps<Props>()
17
14
 
18
15
  const emit = defineEmits<{
19
16
  'update:modelValue': [value: string]
@@ -55,7 +52,7 @@ const { handleKeydown } = useRovingFocus({
55
52
  </script>
56
53
 
57
54
  <template>
58
- <div :class="['mint-tabs', `mint-tabs--${variant}`]" role="tablist">
55
+ <div class="mint-tabs mint-tabs--underline" role="tablist">
59
56
  <button
60
57
  v-for="tab in normalizedTabs"
61
58
  :id="`mint-tab-${tab.id}`"
@@ -63,6 +63,14 @@ const { isOpen, rootRef: popoverRef, triggerRef, close, toggle } = useDropdownSt
63
63
  const showSuccess = ref(false)
64
64
  const showConfirm = ref(false)
65
65
 
66
+ function handleTriggerClick() {
67
+ if (!hasExperiment.value && !isUnresolved.value) {
68
+ handleSelect()
69
+ return
70
+ }
71
+ toggle()
72
+ }
73
+
66
74
  function handleSelect() {
67
75
  emit('select')
68
76
  close()
@@ -115,7 +123,7 @@ onUnmounted(() => {
115
123
  { 'mint-experiment-popover__split--with-save': showSave && experimentName },
116
124
  ]"
117
125
  >
118
- <!-- Left: experiment trigger (opens popover). Prefer the human-readable
126
+ <!-- Left: experiment trigger (selects when empty, otherwise opens popover). Prefer the human-readable
119
127
  name; the formal code, when present, stays in the detail panel. -->
120
128
  <button
121
129
  ref="triggerRef"
@@ -131,7 +139,7 @@ onUnmounted(() => {
131
139
  :aria-label="isUnresolved ? triggerLabel : undefined"
132
140
  :aria-busy="isResolving || undefined"
133
141
  :aria-expanded="isOpen"
134
- @click.stop="toggle"
142
+ @click.stop="handleTriggerClick"
135
143
  >
136
144
  <!-- Flask icon -->
137
145
  <svg class="mint-experiment-popover__trigger-icon" aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -0,0 +1,203 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import FileBrowserModal from './FileBrowserModal.vue'
4
+ import type { FileEntry, FileSelection, ServerMount } from '../types/fileBrowserTypes'
5
+
6
+ const isOpen = ref(true)
7
+
8
+ const mounts: ServerMount[] = [
9
+ {
10
+ id: 'instrument-qe',
11
+ label: '/mnt/instrument-qe',
12
+ count: 4,
13
+ children: [
14
+ {
15
+ id: 'instrument-qe/2026-07',
16
+ label: '2026-07',
17
+ count: 12,
18
+ children: [
19
+ { id: 'instrument-qe/2026-07/raw', label: 'raw', count: '1.2k' },
20
+ { id: 'instrument-qe/2026-07/converted', label: 'converted', count: 840 },
21
+ { id: 'instrument-qe/2026-07/logs', label: 'logs', count: 36 },
22
+ ],
23
+ },
24
+ { id: 'instrument-qe/2026-06', label: '2026-06', count: 9 },
25
+ ],
26
+ },
27
+ { id: 'nas-shared', label: '/mnt/nas-shared', count: 18 },
28
+ { id: 'scratch', label: '/mnt/scratch', count: 7 },
29
+ { id: 'sequencer-01', label: '/mnt/sequencer-01', available: false },
30
+ ]
31
+
32
+ const entries: FileEntry[] = [
33
+ { name: 'calibration', path: '2026-07/hypoxia-run3/raw/calibration', kind: 'directory', child_count: 2 },
34
+ { name: 'blanks', path: '2026-07/hypoxia-run3/raw/blanks', kind: 'directory', child_count: 6 },
35
+ {
36
+ name: 'HeLa_norm_0h_r1.raw',
37
+ path: '2026-07/hypoxia-run3/raw/HeLa_norm_0h_r1.raw',
38
+ kind: 'file',
39
+ size_bytes: 2_040_109_465,
40
+ extension: '.raw',
41
+ },
42
+ {
43
+ name: 'HeLa_norm_0h_r2.raw',
44
+ path: '2026-07/hypoxia-run3/raw/HeLa_norm_0h_r2.raw',
45
+ kind: 'file',
46
+ size_bytes: 2_040_109_465,
47
+ extension: '.raw',
48
+ },
49
+ {
50
+ name: 'HeLa_hypoxia_1pct_6h_r1.raw',
51
+ path: '2026-07/hypoxia-run3/raw/HeLa_hypoxia_1pct_6h_r1.raw',
52
+ kind: 'file',
53
+ size_bytes: 2_254_857_830,
54
+ extension: '.raw',
55
+ },
56
+ {
57
+ name: 'HeLa_hypoxia_1pct_6h_r2.raw',
58
+ path: '2026-07/hypoxia-run3/raw/HeLa_hypoxia_1pct_6h_r2.raw',
59
+ kind: 'file',
60
+ size_bytes: 2_254_857_830,
61
+ extension: '.raw',
62
+ },
63
+ {
64
+ name: 'HeLa_hypoxia_1pct_24h_r1.mzML',
65
+ path: '2026-07/hypoxia-run3/raw/HeLa_hypoxia_1pct_24h_r1.mzML',
66
+ kind: 'file',
67
+ size_bytes: 882_900_992,
68
+ extension: '.mzml',
69
+ },
70
+ {
71
+ name: 'acquisition_log.txt',
72
+ path: '2026-07/hypoxia-run3/raw/acquisition_log.txt',
73
+ kind: 'file',
74
+ size_bytes: 49_152,
75
+ extension: '.txt',
76
+ matches_type_rules: false,
77
+ },
78
+ {
79
+ name: 'sequence_table.csv',
80
+ path: '2026-07/hypoxia-run3/raw/sequence_table.csv',
81
+ kind: 'file',
82
+ size_bytes: 12_288,
83
+ extension: '.csv',
84
+ matches_type_rules: false,
85
+ },
86
+ ]
87
+
88
+ const breadcrumbs = [
89
+ { label: '/mnt/instrument-qe', path: '' },
90
+ { label: '2026-07', path: '2026-07' },
91
+ { label: 'hypoxia-run3', path: '2026-07/hypoxia-run3' },
92
+ { label: 'raw', path: '2026-07/hypoxia-run3/raw' },
93
+ ]
94
+
95
+ const selected: FileSelection[] = [
96
+ { mount_id: 'instrument-qe/2026-07/raw', path: '2026-07/hypoxia-run3/raw/blanks', name: 'blanks', kind: 'directory', child_count: 6 },
97
+ {
98
+ mount_id: 'instrument-qe/2026-07/raw',
99
+ path: '2026-07/hypoxia-run3/raw/HeLa_norm_0h_r1.raw',
100
+ name: 'HeLa_norm_0h_r1.raw',
101
+ kind: 'file',
102
+ size_bytes: 2_040_109_465,
103
+ },
104
+ {
105
+ mount_id: 'instrument-qe/2026-07/raw',
106
+ path: '2026-07/hypoxia-run3/raw/HeLa_hypoxia_1pct_6h_r1.raw',
107
+ name: 'HeLa_hypoxia_1pct_6h_r1.raw',
108
+ kind: 'file',
109
+ size_bytes: 2_254_857_830,
110
+ },
111
+ {
112
+ mount_id: 'instrument-qe/2026-07/raw',
113
+ path: '2026-07/hypoxia-run3/raw/HeLa_hypoxia_1pct_6h_r2.raw',
114
+ name: 'HeLa_hypoxia_1pct_6h_r2.raw',
115
+ kind: 'file',
116
+ size_bytes: 2_254_857_830,
117
+ },
118
+ ]
119
+
120
+ function initState() {
121
+ return {
122
+ open: true,
123
+ title: 'Select raw data',
124
+ contextCode: 'MM-EXP-014',
125
+ subtitle: '13C glucose tracing — HeLa, 24h',
126
+ dimNonMatching: true,
127
+ showLocalPicker: true,
128
+ loading: false,
129
+ selected,
130
+ }
131
+ }
132
+ </script>
133
+
134
+ <template>
135
+ <Story title="Experiment/FileBrowserModal">
136
+ <Variant title="Playground" :init-state="initState">
137
+ <template #default="{ state }">
138
+ <div style="padding: 2rem;">
139
+ <FileBrowserModal
140
+ v-model="state.open"
141
+ :mounts="mounts"
142
+ mount-id="instrument-qe/2026-07/raw"
143
+ path="2026-07/hypoxia-run3/raw"
144
+ parent="2026-07/hypoxia-run3"
145
+ :entries="entries"
146
+ :breadcrumbs="breadcrumbs"
147
+ :selected="state.selected"
148
+ :type-rules="['.raw', '.mzML']"
149
+ :total-count="1284"
150
+ :match-count="9"
151
+ :loading="state.loading"
152
+ :title="state.title"
153
+ :context-code="state.contextCode"
154
+ :subtitle="state.subtitle"
155
+ :dim-non-matching="state.dimNonMatching"
156
+ :show-local-picker="state.showLocalPicker"
157
+ confirm-label="Mount 4 items"
158
+ @update:selected="state.selected = $event"
159
+ />
160
+ </div>
161
+ </template>
162
+ <template #controls="{ state }">
163
+ <HstCheckbox v-model="state.open" title="Open" />
164
+ <HstText v-model="state.title" title="Title" />
165
+ <HstText v-model="state.contextCode" title="Context code" />
166
+ <HstText v-model="state.subtitle" title="Subtitle" />
167
+ <HstCheckbox v-model="state.dimNonMatching" title="Dim non-matching" />
168
+ <HstCheckbox v-model="state.showLocalPicker" title="Show local picker" />
169
+ <HstCheckbox v-model="state.loading" title="Loading" />
170
+ </template>
171
+ </Variant>
172
+
173
+ <Variant title="Empty folder">
174
+ <div style="padding: 2rem;">
175
+ <FileBrowserModal
176
+ :model-value="isOpen"
177
+ :mounts="mounts"
178
+ mount-id="scratch"
179
+ path=""
180
+ :parent="null"
181
+ :entries="[]"
182
+ :breadcrumbs="[{ label: '/mnt/scratch', path: '' }]"
183
+ title="Select raw data"
184
+ />
185
+ </div>
186
+ </Variant>
187
+
188
+ <Variant title="Offline mount">
189
+ <div style="padding: 2rem;">
190
+ <FileBrowserModal
191
+ :model-value="isOpen"
192
+ :mounts="mounts"
193
+ mount-id="sequencer-01"
194
+ path=""
195
+ :parent="null"
196
+ :entries="[]"
197
+ error="This mount is offline. Check that the share is reachable from the MINT server."
198
+ title="Select raw data"
199
+ />
200
+ </div>
201
+ </Variant>
202
+ </Story>
203
+ </template>