@morscherlab/mint-sdk 1.0.0-beta.3 → 1.0.0-beta.5

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 (181) hide show
  1. package/README.md +9 -2
  2. package/dist/__tests__/composables/experiment-utils.test.d.ts +1 -0
  3. package/dist/__tests__/composables/useApi.test.d.ts +1 -0
  4. package/dist/components/AppContainer.vue.d.ts +1 -1
  5. package/dist/components/AppLayout.vue.d.ts +20 -1
  6. package/dist/components/AppSidebar.vue.d.ts +57 -5
  7. package/dist/components/AppTopBar.vue.d.ts +7 -25
  8. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +3 -1
  9. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -0
  10. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +5 -0
  11. package/dist/components/ComponentBindingRenderer.vue.d.ts +44 -0
  12. package/dist/components/ControlWorkspaceView.vue.d.ts +24 -7
  13. package/dist/components/DoseDesignWorkspaceView.vue.d.ts +149 -0
  14. package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
  15. package/dist/components/FormBuilder.vue.d.ts +9 -9
  16. package/dist/components/PlateMapEditor.vue.d.ts +1 -1
  17. package/dist/components/PluginWorkspaceView.vue.d.ts +310 -0
  18. package/dist/components/SettingsModal.vue.d.ts +1 -1
  19. package/dist/components/WellPlate.vue.d.ts +2 -2
  20. package/dist/components/index.d.ts +3 -12
  21. package/dist/components/index.js +3 -3
  22. package/dist/components/{AppPageSelector.vue.d.ts → internal/AppTopBarPageSelectorInternal.vue.d.ts} +1 -1
  23. package/dist/components/{AppPillNav.vue.d.ts → internal/AppTopBarPillNavInternal.vue.d.ts} +3 -1
  24. package/dist/components/{CalendarGridPanel.vue.d.ts → internal/CalendarGridPanelInternal.vue.d.ts} +1 -1
  25. package/dist/components/internal/FormSectionRenderer.vue.d.ts +4 -4
  26. package/dist/components/{WellEditPopup.vue.d.ts → internal/WellEditPopupInternal.vue.d.ts} +1 -1
  27. package/dist/{components-D_Sr0adg.js → components-DihbSJjU.js} +5932 -5408
  28. package/dist/components-DihbSJjU.js.map +1 -0
  29. package/dist/composables/experiment-utils.d.ts +8 -0
  30. package/dist/composables/index.d.ts +5 -7
  31. package/dist/composables/index.js +4 -4
  32. package/dist/composables/useAppExperiment.d.ts +31 -2
  33. package/dist/composables/useBioTemplateComponents.d.ts +5 -3
  34. package/dist/composables/useBioTemplatePackWorkspace.d.ts +3 -2
  35. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +6 -5
  36. package/dist/composables/useBioTemplateWorkspace.d.ts +5 -4
  37. package/dist/composables/useControlSchema.d.ts +43 -21
  38. package/dist/composables/usePluginClient.d.ts +5 -2
  39. package/dist/{composables-C3dpXQN5.js → composables-BcgZ6diz.js} +40 -28
  40. package/dist/composables-BcgZ6diz.js.map +1 -0
  41. package/dist/index.d.ts +5 -12
  42. package/dist/index.js +5 -5
  43. package/dist/install.js +2 -2
  44. package/dist/styles.css +5637 -5663
  45. package/dist/templates/adapters.d.ts +7 -1
  46. package/dist/templates/catalog.d.ts +5 -5
  47. package/dist/templates/componentBindings.d.ts +13 -0
  48. package/dist/templates/index.d.ts +5 -5
  49. package/dist/templates/index.js +2 -2
  50. package/dist/templates/presets.d.ts +4 -4
  51. package/dist/templates/types.d.ts +4 -1
  52. package/dist/{templates-50NPjaxL.js → templates-Cyt0Suwf.js} +322 -73
  53. package/dist/templates-Cyt0Suwf.js.map +1 -0
  54. package/dist/types/components.d.ts +6 -25
  55. package/dist/types/index.d.ts +1 -1
  56. package/dist/{useScheduleDrag-D4oWdh41.js → useExperimentData-CM6Y0u5L.js} +400 -357
  57. package/dist/useExperimentData-CM6Y0u5L.js.map +1 -0
  58. package/package.json +1 -1
  59. package/src/__tests__/components/ActionItem.test.ts +6 -6
  60. package/src/__tests__/components/AppLayout.test.ts +44 -0
  61. package/src/__tests__/components/AppSidebar.test.ts +130 -2
  62. package/src/__tests__/components/AppToastContainer.test.ts +0 -11
  63. package/src/__tests__/components/AppTopBar.test.ts +189 -120
  64. package/src/__tests__/components/{AppPageSelector.test.ts → AppTopBarPageSelector.test.ts} +8 -8
  65. package/src/__tests__/components/{AppPillNav.test.ts → AppTopBarPillNav.test.ts} +53 -6
  66. package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +7 -1
  67. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +32 -1
  68. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +48 -1
  69. package/src/__tests__/components/BioTemplateRenderer.test.ts +25 -0
  70. package/src/__tests__/components/CalendarGridPanel.test.ts +3 -3
  71. package/src/__tests__/components/ComponentBindingRenderer.test.ts +278 -0
  72. package/src/__tests__/components/ControlWorkspaceView.test.ts +134 -63
  73. package/src/__tests__/components/DateTimePicker.test.ts +2 -2
  74. package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +185 -0
  75. package/src/__tests__/components/PluginWorkspaceView.test.ts +548 -0
  76. package/src/__tests__/composables/experiment-utils.test.ts +30 -0
  77. package/src/__tests__/composables/useApi.test.ts +30 -0
  78. package/src/__tests__/composables/useAppExperiment.test.ts +100 -1
  79. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +7 -4
  80. package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +7 -7
  81. package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +6 -1
  82. package/src/__tests__/composables/useControlSchema.test.ts +151 -37
  83. package/src/__tests__/composables/usePluginClient.test.ts +99 -2
  84. package/src/__tests__/docs/frontendDocsCatalog.test.ts +120 -25
  85. package/src/__tests__/templates/templates.test.ts +56 -0
  86. package/src/components/AppAvatarMenu.vue +3 -3
  87. package/src/components/AppLayout.story.vue +39 -0
  88. package/src/components/AppLayout.vue +83 -2
  89. package/src/components/AppPluginSwitcher.vue +5 -5
  90. package/src/components/AppSidebar.story.vue +113 -5
  91. package/src/components/AppSidebar.vue +147 -27
  92. package/src/components/AppTopBar.story.vue +2 -5
  93. package/src/components/AppTopBar.vue +35 -425
  94. package/src/components/BioTemplateExperimentWorkspaceView.story.vue +2 -2
  95. package/src/components/BioTemplateExperimentWorkspaceView.vue +6 -0
  96. package/src/components/BioTemplatePackWorkspaceView.story.vue +4 -4
  97. package/src/components/BioTemplatePackWorkspaceView.vue +1 -0
  98. package/src/components/BioTemplatePresetWorkspaceView.story.vue +14 -2
  99. package/src/components/BioTemplatePresetWorkspaceView.vue +12 -3
  100. package/src/components/BioTemplateRenderer.story.vue +2 -2
  101. package/src/components/BioTemplateRenderer.vue +15 -227
  102. package/src/components/ComponentBindingRenderer.story.vue +87 -0
  103. package/src/components/ComponentBindingRenderer.vue +317 -0
  104. package/src/components/ControlWorkspaceView.story.vue +20 -9
  105. package/src/components/ControlWorkspaceView.vue +43 -12
  106. package/src/components/DatePicker.vue +2 -2
  107. package/src/components/DateTimePicker.vue +2 -2
  108. package/src/components/DoseDesignWorkspaceView.story.vue +77 -0
  109. package/src/components/DoseDesignWorkspaceView.vue +255 -0
  110. package/src/components/ExperimentPopover.story.vue +2 -2
  111. package/src/components/ExperimentPopover.vue +2 -6
  112. package/src/components/ExperimentSelectorModal.vue +6 -5
  113. package/src/components/FormBuilder.story.vue +190 -0
  114. package/src/components/PluginWorkspaceView.story.vue +334 -0
  115. package/src/components/PluginWorkspaceView.vue +708 -0
  116. package/src/components/SettingsModal.story.vue +87 -0
  117. package/src/components/WellPlate.vue +2 -2
  118. package/src/components/index.ts +3 -12
  119. package/src/components/{AppPageSelector.vue → internal/AppTopBarPageSelectorInternal.vue} +9 -9
  120. package/src/components/internal/AppTopBarPillNavInternal.vue +194 -0
  121. package/src/components/{CalendarGridPanel.vue → internal/CalendarGridPanelInternal.vue} +1 -1
  122. package/src/components/{WellEditPopup.vue → internal/WellEditPopupInternal.vue} +3 -3
  123. package/src/composables/experiment-utils.ts +26 -0
  124. package/src/composables/index.ts +21 -7
  125. package/src/composables/useApi.ts +9 -2
  126. package/src/composables/useAppExperiment.ts +85 -13
  127. package/src/composables/useBioTemplateComponents.ts +12 -0
  128. package/src/composables/useBioTemplatePackWorkspace.ts +6 -2
  129. package/src/composables/useBioTemplatePresetWorkspace.ts +10 -21
  130. package/src/composables/useBioTemplateWorkspace.ts +6 -4
  131. package/src/composables/useControlSchema.ts +157 -69
  132. package/src/composables/usePluginClient.ts +50 -9
  133. package/src/index.ts +6 -563
  134. package/src/styles/components/app-layout.css +82 -0
  135. package/src/styles/components/app-page-selector.css +1 -1
  136. package/src/styles/components/app-pill-nav.css +71 -1
  137. package/src/styles/components/app-sidebar.css +119 -0
  138. package/src/styles/components/app-top-bar.css +0 -235
  139. package/src/styles/components/experiment-popover.css +2 -2
  140. package/src/styles/index.css +0 -1
  141. package/src/templates/adapters.ts +193 -0
  142. package/src/templates/catalog.ts +5 -5
  143. package/src/templates/componentBindings.ts +90 -3
  144. package/src/templates/index.ts +10 -0
  145. package/src/templates/packs.ts +10 -1
  146. package/src/templates/presets.ts +14 -4
  147. package/src/templates/types.ts +4 -0
  148. package/src/types/components.ts +6 -31
  149. package/src/types/index.ts +2 -6
  150. package/dist/__tests__/composables/usePluginApi.test.d.ts +0 -13
  151. package/dist/components/FormFieldRenderer.vue.d.ts +0 -28
  152. package/dist/components/FormSection.vue.d.ts +0 -30
  153. package/dist/components/GroupingModal.vue.d.ts +0 -12
  154. package/dist/components/SettingsButton.vue.d.ts +0 -30
  155. package/dist/components/ToastNotification.vue.d.ts +0 -2
  156. package/dist/components-D_Sr0adg.js.map +0 -1
  157. package/dist/composables/usePluginApi.d.ts +0 -22
  158. package/dist/composables-C3dpXQN5.js.map +0 -1
  159. package/dist/templates-50NPjaxL.js.map +0 -1
  160. package/dist/useScheduleDrag-D4oWdh41.js.map +0 -1
  161. package/src/__tests__/components/FormCompatibility.test.ts +0 -94
  162. package/src/__tests__/components/GroupingModal.test.ts +0 -73
  163. package/src/__tests__/components/SettingsButton.test.ts +0 -44
  164. package/src/__tests__/composables/usePluginApi.test.ts +0 -81
  165. package/src/components/AppPillNav.vue +0 -71
  166. package/src/components/FormFieldRenderer.vue +0 -35
  167. package/src/components/FormSection.vue +0 -37
  168. package/src/components/GroupingModal.story.vue +0 -52
  169. package/src/components/GroupingModal.vue +0 -61
  170. package/src/components/SettingsButton.story.vue +0 -58
  171. package/src/components/SettingsButton.vue +0 -64
  172. package/src/components/ToastNotification.vue +0 -9
  173. package/src/composables/usePluginApi.ts +0 -32
  174. package/src/styles/components/settings-button.css +0 -31
  175. /package/dist/__tests__/components/{AppPageSelector.test.d.ts → AppTopBarPageSelector.test.d.ts} +0 -0
  176. /package/dist/__tests__/components/{AppPillNav.test.d.ts → AppTopBarPillNav.test.d.ts} +0 -0
  177. /package/dist/__tests__/components/{FormCompatibility.test.d.ts → ComponentBindingRenderer.test.d.ts} +0 -0
  178. /package/dist/__tests__/components/{GroupingModal.test.d.ts → DoseDesignWorkspaceView.test.d.ts} +0 -0
  179. /package/dist/__tests__/components/{SettingsButton.test.d.ts → PluginWorkspaceView.test.d.ts} +0 -0
  180. /package/dist/components/{ActionItem.vue.d.ts → internal/ActionItemInternal.vue.d.ts} +0 -0
  181. /package/src/components/{ActionItem.vue → internal/ActionItemInternal.vue} +0 -0
@@ -0,0 +1,255 @@
1
+ <script setup lang="ts">
2
+ /** Complete ControlWorkspaceView page shell for WellPlate + DoseCalculator dose design. */
3
+ import { computed } from 'vue'
4
+ import type { TopBarVariant } from '../types'
5
+ import type { FormEnhancements } from '../types/form-builder'
6
+ import type {
7
+ ControlModel,
8
+ ControlModelBinding,
9
+ ControlSchema,
10
+ ControlWorkspaceOptions,
11
+ DoseDesignControlModelOptions,
12
+ UseControlWorkspaceReturn,
13
+ } from '../composables/useControlSchema'
14
+ import { defineDoseDesignControlModel } from '../composables/useControlSchema'
15
+ import ControlWorkspaceView from './ControlWorkspaceView.vue'
16
+ import DoseCalculator from './DoseCalculator.vue'
17
+ import WellPlate from './WellPlate.vue'
18
+
19
+ type DoseDesignSidebarVariant = 'default' | 'analysis'
20
+ type ComponentProps = Record<string, unknown>
21
+ type ComponentPropsById = Record<string, ComponentProps>
22
+ type ComponentBinding = { id: string, component: string, props: ComponentProps }
23
+ type ComponentBindingsById = Record<string, ComponentBinding>
24
+ type ResolvedControlWorkspace = UseControlWorkspaceReturn<ControlSchema>
25
+
26
+ interface DoseDesignWorkspaceSlotProps {
27
+ workspace: ResolvedControlWorkspace
28
+ bindings: ResolvedControlWorkspace['bindings']
29
+ values: ResolvedControlWorkspace['values']
30
+ componentBindings: ComponentBinding[]
31
+ componentBindingsById: ComponentBindingsById
32
+ componentPropsById: ComponentPropsById
33
+ wellPlateProps: ComponentProps
34
+ doseCalculatorProps: ComponentProps
35
+ }
36
+
37
+ interface Props {
38
+ /** Model returned by defineDoseDesignControlModel(), or a custom compatible ControlWorkspace model. */
39
+ model?: ControlModel | ControlModelBinding
40
+ /** Workspace returned by useControlWorkspace(). Use for full manual control. */
41
+ workspace?: UseControlWorkspaceReturn<ControlSchema>
42
+ /** Options used when this view creates the default dose-design model internally. */
43
+ doseDesignOptions?: DoseDesignControlModelOptions
44
+ /** Options passed to the internally generated ControlWorkspaceView workspace. */
45
+ controlOptions?: ControlWorkspaceOptions
46
+ /** Initial values for the internally generated workspace. */
47
+ initialValues?: Record<string, unknown>
48
+ /** External values for the internally generated workspace. Supports default v-model. */
49
+ modelValue?: Record<string, unknown>
50
+ /** External values for the internally generated workspace. Supports v-model:values. */
51
+ values?: Record<string, unknown>
52
+ /** Named component props id for the generated WellPlate binding. */
53
+ plateId?: string
54
+ /** Named component props id for the generated DoseCalculator binding. */
55
+ doseId?: string
56
+ /** Extra props merged into the generated WellPlate binding. */
57
+ wellPlateProps?: ComponentProps
58
+ /** Extra props merged into the generated DoseCalculator binding. */
59
+ doseCalculatorProps?: ComponentProps
60
+ /** AppTopBar title. */
61
+ title?: string
62
+ /** AppTopBar subtitle. */
63
+ subtitle?: string
64
+ /** AppTopBar visual variant. */
65
+ topBarVariant?: TopBarVariant
66
+ /** AppSidebar/AppLayout sidebar width. */
67
+ sidebarWidth?: string
68
+ /** AppSidebar visual preset. `analysis` matches the LEAF-style MINT analysis sidebar design language. */
69
+ sidebarVariant?: DoseDesignSidebarVariant
70
+ /** Convert the sidebar into an SDK-owned mobile overlay below the AppLayout breakpoint. */
71
+ responsiveSidebar?: boolean
72
+ /** Sidebar position in AppLayout. */
73
+ sidebarPosition?: 'left' | 'right'
74
+ /** Optional AppSidebar chrome title for LEAF-style plugin workbenches. */
75
+ sidebarTitle?: string
76
+ /** Optional AppSidebar chrome subtitle for active experiment/run context. */
77
+ sidebarSubtitle?: string
78
+ /** Optional compact badge/count rendered in the AppSidebar chrome header. */
79
+ sidebarBadge?: string | number
80
+ /** Floating AppLayout style. */
81
+ floating?: boolean
82
+ /** Compact AppSidebar density. */
83
+ dense?: boolean
84
+ /** Whether AppTopBar should show generated settings. */
85
+ showSettings?: boolean
86
+ /** Render FormBuilder actions in the default generated forms. */
87
+ showFormActions?: boolean
88
+ /** Runtime FormBuilder enhancements passed to generated forms. */
89
+ formEnhancements?: FormEnhancements<Record<string, unknown>>
90
+ /** Loading/saving state passed to generated forms. */
91
+ formLoading?: boolean
92
+ /** Disabled state passed to generated forms. */
93
+ formDisabled?: boolean
94
+ /** Readonly state passed to generated forms. */
95
+ formReadonly?: boolean
96
+ /** FormBuilder size in generated forms. */
97
+ formSize?: 'sm' | 'md' | 'lg'
98
+ }
99
+
100
+ const props = withDefaults(defineProps<Props>(), {
101
+ model: undefined,
102
+ workspace: undefined,
103
+ doseDesignOptions: () => ({}),
104
+ controlOptions: () => ({}),
105
+ initialValues: undefined,
106
+ modelValue: undefined,
107
+ values: undefined,
108
+ plateId: undefined,
109
+ doseId: undefined,
110
+ wellPlateProps: () => ({}),
111
+ doseCalculatorProps: () => ({}),
112
+ title: 'Dose Design Workspace',
113
+ subtitle: undefined,
114
+ topBarVariant: 'card',
115
+ sidebarWidth: '320px',
116
+ sidebarVariant: 'analysis',
117
+ responsiveSidebar: true,
118
+ sidebarPosition: 'left',
119
+ sidebarTitle: undefined,
120
+ sidebarSubtitle: undefined,
121
+ sidebarBadge: undefined,
122
+ floating: false,
123
+ dense: true,
124
+ showSettings: true,
125
+ showFormActions: false,
126
+ formEnhancements: undefined,
127
+ formLoading: false,
128
+ formDisabled: false,
129
+ formReadonly: false,
130
+ formSize: 'md',
131
+ })
132
+
133
+ const emit = defineEmits<{
134
+ /** Emitted when generated workspace values change through WellPlate, FormBuilder, AppSidebar, or AppTopBar settings. */
135
+ 'update:modelValue': [values: Record<string, unknown>]
136
+ /** Emitted when generated workspace values change through WellPlate, FormBuilder, AppSidebar, or AppTopBar settings. */
137
+ 'update:values': [values: Record<string, unknown>]
138
+ /** Forwarded from the internal ControlWorkspaceView when form actions are enabled. */
139
+ submit: [values: Record<string, unknown>]
140
+ /** Forwarded from the internal ControlWorkspaceView when form actions are enabled. */
141
+ cancel: []
142
+ }>()
143
+
144
+ defineSlots<{
145
+ default?: (props: DoseDesignWorkspaceSlotProps) => unknown
146
+ plate?: (props: DoseDesignWorkspaceSlotProps) => unknown
147
+ dose?: (props: DoseDesignWorkspaceSlotProps) => unknown
148
+ }>()
149
+
150
+ const resolvedModel = computed<ControlModel | ControlModelBinding>(() =>
151
+ props.model ?? defineDoseDesignControlModel(props.doseDesignOptions)
152
+ )
153
+
154
+ function resolvedPlateId(): string {
155
+ return props.plateId ?? props.doseDesignOptions.componentProps?.plateId ?? 'plate'
156
+ }
157
+
158
+ function resolvedDoseId(): string {
159
+ return props.doseId ?? props.doseDesignOptions.componentProps?.doseId ?? 'dose'
160
+ }
161
+
162
+ function wellPlateBinding(componentPropsById: ComponentPropsById): ComponentProps {
163
+ return {
164
+ size: 'fill',
165
+ selectionMode: 'multiple',
166
+ ...(componentPropsById[resolvedPlateId()] ?? {}),
167
+ ...props.wellPlateProps,
168
+ }
169
+ }
170
+
171
+ function doseCalculatorBinding(componentPropsById: ComponentPropsById): ComponentProps {
172
+ return {
173
+ ...(componentPropsById[resolvedDoseId()] ?? {}),
174
+ ...props.doseCalculatorProps,
175
+ }
176
+ }
177
+
178
+ function slotProps(slotProps: {
179
+ workspace: ResolvedControlWorkspace
180
+ bindings: ResolvedControlWorkspace['bindings']
181
+ values: ResolvedControlWorkspace['values']
182
+ componentBindings: ComponentBinding[]
183
+ componentBindingsById: ComponentBindingsById
184
+ componentPropsById: ComponentPropsById
185
+ }): DoseDesignWorkspaceSlotProps {
186
+ return {
187
+ ...slotProps,
188
+ wellPlateProps: wellPlateBinding(slotProps.componentPropsById),
189
+ doseCalculatorProps: doseCalculatorBinding(slotProps.componentPropsById),
190
+ }
191
+ }
192
+ </script>
193
+
194
+ <template>
195
+ <ControlWorkspaceView
196
+ :model="resolvedModel"
197
+ :workspace="workspace"
198
+ :control-options="controlOptions"
199
+ :initial-values="initialValues"
200
+ :model-value="modelValue"
201
+ :values="values"
202
+ :title="title"
203
+ :subtitle="subtitle"
204
+ :top-bar-variant="topBarVariant"
205
+ :sidebar-width="sidebarWidth"
206
+ :sidebar-variant="sidebarVariant"
207
+ :responsive-sidebar="responsiveSidebar"
208
+ :sidebar-position="sidebarPosition"
209
+ :sidebar-title="sidebarTitle"
210
+ :sidebar-subtitle="sidebarSubtitle"
211
+ :sidebar-badge="sidebarBadge"
212
+ :floating="floating"
213
+ :dense="dense"
214
+ :show-settings="showSettings"
215
+ :show-form-actions="showFormActions"
216
+ :form-enhancements="formEnhancements"
217
+ :form-loading="formLoading"
218
+ :form-disabled="formDisabled"
219
+ :form-readonly="formReadonly"
220
+ :form-size="formSize"
221
+ @update:model-value="emit('update:modelValue', $event)"
222
+ @update:values="emit('update:values', $event)"
223
+ @submit="emit('submit', $event)"
224
+ @cancel="emit('cancel')"
225
+ >
226
+ <template #default="workspaceSlotProps">
227
+ <slot v-bind="slotProps(workspaceSlotProps)">
228
+ <div class="mint-dose-design-workspace">
229
+ <slot name="plate" v-bind="slotProps(workspaceSlotProps)">
230
+ <WellPlate v-bind="wellPlateBinding(workspaceSlotProps.componentPropsById)" />
231
+ </slot>
232
+ <slot name="dose" v-bind="slotProps(workspaceSlotProps)">
233
+ <DoseCalculator v-bind="doseCalculatorBinding(workspaceSlotProps.componentPropsById)" />
234
+ </slot>
235
+ </div>
236
+ </slot>
237
+ </template>
238
+ </ControlWorkspaceView>
239
+ </template>
240
+
241
+ <style scoped>
242
+ .mint-dose-design-workspace {
243
+ display: grid;
244
+ grid-template-columns: minmax(360px, 1fr) minmax(300px, 420px);
245
+ gap: 1rem;
246
+ align-items: start;
247
+ min-width: 0;
248
+ }
249
+
250
+ @media (max-width: 900px) {
251
+ .mint-dose-design-workspace {
252
+ grid-template-columns: minmax(0, 1fr);
253
+ }
254
+ }
255
+ </style>
@@ -283,7 +283,7 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
283
283
  </div>
284
284
  </div>
285
285
  <p style="margin-top: 1rem; font-size: 0.75rem; color: var(--text-muted); text-align: center;">
286
- Same 2.375rem height and refresh-design motion as AppPluginSwitcher, AppPageSelector, AppAvatarMenu.
286
+ Same 2.375rem height and refresh-design motion as AppPluginSwitcher, AppTopBar page selector, AppAvatarMenu.
287
287
  </p>
288
288
  </div>
289
289
  </Variant>
@@ -363,7 +363,7 @@ optionally pairs with an inline Save button as a split unit.
363
363
 
364
364
  ### Refresh-design alignment
365
365
 
366
- - **Height** `2.375rem` — matches `AppPluginSwitcher` / `AppPageSelector` so the
366
+ - **Height** `2.375rem` — matches `AppPluginSwitcher` / `AppTopBar page selector` so the
367
367
  topbar's right cluster reads at one optical baseline.
368
368
  - **Surface** `--bg-card` + `--border-color` — consistent with the white-card +
369
369
  border language.
@@ -2,6 +2,7 @@
2
2
  /** Floating popover showing the active experiment with save, detach, and select actions. */
3
3
  import { ref, watch, onUnmounted } from 'vue'
4
4
  import { useDropdownState } from '../composables/useDropdownState'
5
+ import { formatExperimentStatus } from '../composables/experiment-utils'
5
6
  import ConfirmDialog from './ConfirmDialog.vue'
6
7
 
7
8
  interface Props {
@@ -80,11 +81,6 @@ watch(() => props.saveSuccessMessage, (msg) => {
80
81
  onUnmounted(() => {
81
82
  if (successTimer) clearTimeout(successTimer)
82
83
  })
83
-
84
- // Format status for display (e.g., "ready_to_extract" -> "Ready to extract")
85
- function formatStatus(status: string): string {
86
- return status.replace(/_/g, ' ').replace(/^\w/, c => c.toUpperCase())
87
- }
88
84
  </script>
89
85
 
90
86
  <template>
@@ -191,7 +187,7 @@ function formatStatus(status: string): string {
191
187
  <span v-if="experimentCode" class="mint-experiment-popover__card-code">{{ experimentCode }}</span>
192
188
  <div class="mint-experiment-popover__card-name">{{ experimentName }}</div>
193
189
  <div v-if="experimentStatus" class="mint-experiment-popover__card-status">
194
- {{ formatStatus(experimentStatus) }}
190
+ {{ formatExperimentStatus(experimentStatus) }}
195
191
  </div>
196
192
  </div>
197
193
  </div>
@@ -5,8 +5,9 @@ import type { ModalSize, ExperimentSummary, ExperimentFilters } from '../types'
5
5
  import { useExperimentSelector } from '../composables/useExperimentSelector'
6
6
  import {
7
7
  formatExperimentDate,
8
+ formatExperimentStatus,
9
+ getExperimentStatusVariant,
8
10
  EXPERIMENT_STATUS_OPTIONS,
9
- EXPERIMENT_STATUS_VARIANT_MAP,
10
11
  DATE_PRESET_OPTIONS,
11
12
  SORT_OPTIONS,
12
13
  } from '../composables/experiment-utils'
@@ -326,8 +327,8 @@ watch(
326
327
  <span>{{ formatExperimentDate(exp.created_at) }}</span>
327
328
  </div>
328
329
  </div>
329
- <BasePill :variant="EXPERIMENT_STATUS_VARIANT_MAP[exp.status]" size="sm">
330
- {{ exp.status }}
330
+ <BasePill :variant="getExperimentStatusVariant(exp.status)" size="sm">
331
+ {{ formatExperimentStatus(exp.status) }}
331
332
  </BasePill>
332
333
  </div>
333
334
  </template>
@@ -362,8 +363,8 @@ watch(
362
363
  <span>{{ formatExperimentDate(exp.created_at) }}</span>
363
364
  </div>
364
365
  </div>
365
- <BasePill :variant="EXPERIMENT_STATUS_VARIANT_MAP[exp.status]" size="sm">
366
- {{ exp.status }}
366
+ <BasePill :variant="getExperimentStatusVariant(exp.status)" size="sm">
367
+ {{ formatExperimentStatus(exp.status) }}
367
368
  </BasePill>
368
369
  </div>
369
370
  </div>
@@ -0,0 +1,190 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import FormBuilder from './FormBuilder.vue'
4
+ import { defineControlModel, defineControls } from '../composables/useControlSchema'
5
+
6
+ type FormSize = 'sm' | 'md' | 'lg'
7
+
8
+ const analysisModel = defineControlModel({
9
+ views: {
10
+ analysis: {
11
+ label: 'Analysis',
12
+ sections: {
13
+ parameters: {
14
+ label: 'Parameters',
15
+ description: 'Model and threshold controls',
16
+ columns: 2,
17
+ controls: {
18
+ threshold: {
19
+ type: 'number',
20
+ label: 'Threshold',
21
+ default: 0.05,
22
+ min: 0,
23
+ max: 1,
24
+ props: { step: 0.01 },
25
+ },
26
+ method: {
27
+ label: 'Method',
28
+ default: 'logistic',
29
+ options: ['linear', 'logistic', 'spline'],
30
+ },
31
+ normalize: {
32
+ label: 'Normalize intensities',
33
+ default: true,
34
+ type: 'toggle',
35
+ },
36
+ },
37
+ },
38
+ quality: {
39
+ label: 'Quality Control',
40
+ description: 'Result filters',
41
+ columns: 2,
42
+ controls: {
43
+ minPeakArea: {
44
+ type: 'number',
45
+ label: 'Min peak area',
46
+ default: 1000,
47
+ min: 0,
48
+ },
49
+ excludeOutliers: {
50
+ label: 'Exclude outliers',
51
+ default: true,
52
+ type: 'checkbox',
53
+ },
54
+ },
55
+ },
56
+ },
57
+ },
58
+ },
59
+ })
60
+
61
+ const compactControls = defineControls({
62
+ sampleName: {
63
+ label: 'Sample name',
64
+ default: 'Vehicle',
65
+ required: true,
66
+ section: 'sample',
67
+ sectionLabel: 'Sample',
68
+ },
69
+ concentration: {
70
+ type: 'number',
71
+ label: 'Concentration',
72
+ default: 10,
73
+ min: 0,
74
+ section: 'sample',
75
+ },
76
+ unit: {
77
+ label: 'Unit',
78
+ default: 'uM',
79
+ options: ['nM', 'uM', 'mM'],
80
+ section: 'sample',
81
+ },
82
+ })
83
+
84
+ const modelValues = ref<Record<string, unknown>>({})
85
+ const controlValues = ref<Record<string, unknown>>({})
86
+
87
+ function initState() {
88
+ return {
89
+ size: 'md' as FormSize,
90
+ showActions: true,
91
+ readonly: false,
92
+ disabled: false,
93
+ }
94
+ }
95
+ </script>
96
+
97
+ <template>
98
+ <Story title="Forms/FormBuilder" :init-state="initState">
99
+ <Variant title="Model Driven">
100
+ <template #default="{ state }">
101
+ <div class="form-builder-story">
102
+ <div class="form-builder-story__surface">
103
+ <FormBuilder
104
+ v-model="modelValues"
105
+ :model="analysisModel"
106
+ :size="state.size"
107
+ :show-actions="state.showActions"
108
+ :readonly="state.readonly"
109
+ :disabled="state.disabled"
110
+ />
111
+ </div>
112
+ <pre class="form-builder-story__values">{{ JSON.stringify(modelValues, null, 2) }}</pre>
113
+ </div>
114
+ </template>
115
+
116
+ <template #controls="{ state }">
117
+ <HstSelect
118
+ v-model="state.size"
119
+ title="Size"
120
+ :options="[
121
+ { label: 'Small', value: 'sm' },
122
+ { label: 'Medium', value: 'md' },
123
+ { label: 'Large', value: 'lg' },
124
+ ]"
125
+ />
126
+ <HstCheckbox v-model="state.showActions" title="Actions" />
127
+ <HstCheckbox v-model="state.readonly" title="Readonly" />
128
+ <HstCheckbox v-model="state.disabled" title="Disabled" />
129
+ </template>
130
+ </Variant>
131
+
132
+ <Variant title="Compact Controls">
133
+ <div class="form-builder-story">
134
+ <div class="form-builder-story__surface form-builder-story__surface--narrow">
135
+ <FormBuilder
136
+ v-model="controlValues"
137
+ :controls="compactControls"
138
+ :show-actions="false"
139
+ />
140
+ </div>
141
+ <pre class="form-builder-story__values">{{ JSON.stringify(controlValues, null, 2) }}</pre>
142
+ </div>
143
+ </Variant>
144
+ </Story>
145
+ </template>
146
+
147
+ <style scoped>
148
+ .form-builder-story {
149
+ display: grid;
150
+ grid-template-columns: minmax(0, 1fr) minmax(14rem, 20rem);
151
+ gap: 1rem;
152
+ align-items: start;
153
+ padding: 2rem;
154
+ min-height: 640px;
155
+ background: var(--bg-secondary, #f8fafc);
156
+ }
157
+
158
+ .form-builder-story__surface {
159
+ min-width: 0;
160
+ padding: 1rem;
161
+ border: 1px solid var(--border-color, #e2e8f0);
162
+ border-radius: var(--radius-md, 0.5rem);
163
+ background: var(--bg-primary, #fff);
164
+ }
165
+
166
+ .form-builder-story__surface--narrow {
167
+ max-width: 34rem;
168
+ }
169
+
170
+ .form-builder-story__values {
171
+ min-width: 0;
172
+ margin: 0;
173
+ padding: 0.875rem;
174
+ border: 1px solid var(--border-color, #e2e8f0);
175
+ border-radius: var(--radius-md, 0.5rem);
176
+ background: var(--bg-tertiary, #f1f5f9);
177
+ color: var(--text-secondary, #475569);
178
+ font-family: var(--font-mono, monospace);
179
+ font-size: 0.75rem;
180
+ line-height: 1.5;
181
+ overflow: auto;
182
+ }
183
+
184
+ @media (max-width: 900px) {
185
+ .form-builder-story {
186
+ grid-template-columns: 1fr;
187
+ padding: 1rem;
188
+ }
189
+ }
190
+ </style>