@morscherlab/mint-sdk 1.0.0-rc.5 → 1.0.0-rc.7
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.
- package/dist/components/AppSidebar.vue.d.ts +6 -3
- package/dist/components/BaseCheckbox.vue.d.ts +6 -1
- package/dist/components/BasePill.vue.d.ts +1 -1
- package/dist/components/BaseRadioGroup.vue.d.ts +1 -1
- package/dist/components/BaseToggle.vue.d.ts +6 -1
- package/dist/components/CollapsibleCard.vue.d.ts +9 -0
- package/dist/components/ControlWorkspaceView.vue.d.ts +1 -1
- package/dist/components/DoseDesignWorkspaceView.vue.d.ts +1 -1
- package/dist/components/PluginWorkspaceView.vue.d.ts +5 -1
- package/dist/components/index.js +2 -2
- package/dist/{components-DtHA2bgp.js → components-Blx4MG--.js} +565 -1477
- package/dist/components-Blx4MG--.js.map +1 -0
- package/dist/composables/controlSchemaTypes.d.ts +7 -1
- package/dist/composables/index.js +3 -3
- package/dist/{composables-Dlg8jenH.js → composables-CHDjDIQT.js} +2 -2
- package/dist/{composables-Dlg8jenH.js.map → composables-CHDjDIQT.js.map} +1 -1
- package/dist/index.js +4 -4
- package/dist/install.js +2 -2
- package/dist/styles.css +867 -126
- package/dist/templates/index.js +1 -1
- package/dist/{templates-DtdUvJ4c.js → templates-DSbHJC4v.js} +1351 -120
- package/dist/templates-DSbHJC4v.js.map +1 -0
- package/dist/types/components.d.ts +12 -0
- package/dist/types/form-builder.d.ts +3 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/{useProtocolTemplates-Bm5vyH4_.js → useProtocolTemplates-BbPOYPzO.js} +2 -2
- package/dist/{useProtocolTemplates-Bm5vyH4_.js.map → useProtocolTemplates-BbPOYPzO.js.map} +1 -1
- package/package.json +1 -1
- package/src/__tests__/components/AppSidebar.test.ts +67 -0
- package/src/__tests__/components/CollapsibleCard.test.ts +47 -0
- package/src/__tests__/components/FormBuilder.test.ts +57 -0
- package/src/__tests__/components/PluginWorkspaceView.shell.test.ts +2 -0
- package/src/components/AppSidebar.story.vue +79 -6
- package/src/components/AppSidebar.vue +72 -0
- package/src/components/BaseCheckbox.story.vue +27 -0
- package/src/components/BaseCheckbox.vue +63 -1
- package/src/components/BaseToggle.story.vue +27 -0
- package/src/components/BaseToggle.vue +66 -1
- package/src/components/CollapsibleCard.vue +123 -45
- package/src/components/FormBuilder.story.vue +13 -0
- package/src/components/PluginWorkspaceView.shell.ts +1 -0
- package/src/components/PluginWorkspaceView.vue +3 -0
- package/src/components/internal/FormFieldRendererInternal.vue +23 -5
- package/src/composables/controlSchemaAdapters.ts +3 -0
- package/src/composables/controlSchemaFormFields.ts +3 -0
- package/src/composables/controlSchemaTypes.ts +8 -0
- package/src/styles/components/app-sidebar.css +134 -6
- package/src/styles/components/checkbox.css +87 -0
- package/src/styles/components/collapsible-card.css +154 -14
- package/src/styles/components/toggle.css +80 -0
- package/src/types/components.ts +21 -0
- package/src/types/form-builder.ts +3 -0
- package/src/types/index.ts +2 -0
- package/dist/components-DtHA2bgp.js.map +0 -1
- package/dist/templates-DtdUvJ4c.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morscherlab/mint-sdk",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.7",
|
|
4
4
|
"description": "MINT Platform SDK — Vue 3 components, composables, and types for plugin development. MINT = Mass-spec INtegrated Toolkit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -238,6 +238,46 @@ describe('AppSidebar', () => {
|
|
|
238
238
|
expect(wrapper.findAllComponents(CollapsibleCard)).toHaveLength(0)
|
|
239
239
|
})
|
|
240
240
|
|
|
241
|
+
it('renders a default collapsed rail from active sections', () => {
|
|
242
|
+
const wrapper = mount(AppSidebar, {
|
|
243
|
+
props: {
|
|
244
|
+
panels: {
|
|
245
|
+
analysis: [
|
|
246
|
+
{
|
|
247
|
+
id: 'filters',
|
|
248
|
+
label: 'Feature Filters',
|
|
249
|
+
icon: 'M3 4.5h18m-15 6h12m-9 6h6',
|
|
250
|
+
badge: 2,
|
|
251
|
+
badgeTone: 'warning',
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
activeView: 'analysis',
|
|
256
|
+
collapsible: true,
|
|
257
|
+
collapsed: true,
|
|
258
|
+
},
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
expect(wrapper.find('.mint-sidebar__rail-item').exists()).toBe(true)
|
|
262
|
+
expect(wrapper.find('.mint-sidebar__rail-svg').exists()).toBe(true)
|
|
263
|
+
expect(wrapper.find('.mint-sidebar__rail-badge').text()).toBe('2')
|
|
264
|
+
expect(wrapper.find('.mint-sidebar__rail-badge').classes()).toContain('mint-sidebar__rail-badge--warning')
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('expands when a default collapsed rail item is clicked', async () => {
|
|
268
|
+
const wrapper = mount(AppSidebar, {
|
|
269
|
+
props: {
|
|
270
|
+
panels: samplePanels,
|
|
271
|
+
activeView: 'analysis',
|
|
272
|
+
collapsible: true,
|
|
273
|
+
defaultCollapsed: true,
|
|
274
|
+
},
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
await wrapper.get('.mint-sidebar__rail-item').trigger('click')
|
|
278
|
+
expect(wrapper.emitted('update:collapsed')).toEqual([[false]])
|
|
279
|
+
})
|
|
280
|
+
|
|
241
281
|
it('renders an empty analysis shell for route-owned sidebar content', () => {
|
|
242
282
|
const wrapper = mount(AppSidebar, {
|
|
243
283
|
props: {
|
|
@@ -297,6 +337,33 @@ describe('AppSidebar', () => {
|
|
|
297
337
|
expect(cards[0].props('icon')).toBe('⚙')
|
|
298
338
|
})
|
|
299
339
|
|
|
340
|
+
it('passes section badges and actions to CollapsibleCard and re-emits actions', async () => {
|
|
341
|
+
const wrapper = mount(AppSidebar, {
|
|
342
|
+
props: {
|
|
343
|
+
panels: {
|
|
344
|
+
analysis: [
|
|
345
|
+
{
|
|
346
|
+
id: 'filters',
|
|
347
|
+
label: 'Feature Filters',
|
|
348
|
+
badge: 4,
|
|
349
|
+
badgeTone: 'cta',
|
|
350
|
+
actions: [{ id: 'reset', label: 'Reset filters' }],
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
},
|
|
354
|
+
activeView: 'analysis',
|
|
355
|
+
},
|
|
356
|
+
global: { components: { CollapsibleCard } },
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
const card = wrapper.findComponent(CollapsibleCard)
|
|
360
|
+
expect(card.props('badge')).toBe(4)
|
|
361
|
+
expect(card.props('badgeTone')).toBe('cta')
|
|
362
|
+
|
|
363
|
+
await card.vm.$emit('action', 'reset')
|
|
364
|
+
expect(wrapper.emitted('section-action')).toEqual([['filters', 'reset']])
|
|
365
|
+
})
|
|
366
|
+
|
|
300
367
|
it('should respect defaultOpen on sections', () => {
|
|
301
368
|
const wrapper = mount(AppSidebar, {
|
|
302
369
|
props: { panels: samplePanels, activeView: 'analysis' },
|
|
@@ -423,6 +423,53 @@ describe('CollapsibleCard', () => {
|
|
|
423
423
|
})
|
|
424
424
|
})
|
|
425
425
|
|
|
426
|
+
describe('badges and header actions', () => {
|
|
427
|
+
it('renders a compact badge in the header controls area', () => {
|
|
428
|
+
const wrapper = mount(CollapsibleCard, {
|
|
429
|
+
props: { title: 'Feature Filters', badge: 3, badgeTone: 'warning' },
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
const badge = wrapper.get('.mint-collapsible-card__badge')
|
|
433
|
+
expect(badge.text()).toBe('3')
|
|
434
|
+
expect(badge.classes()).toContain('mint-collapsible-card__badge--warning')
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
it('renders icon-only header actions and emits their ids', async () => {
|
|
438
|
+
const wrapper = mount(CollapsibleCard, {
|
|
439
|
+
props: {
|
|
440
|
+
title: 'Feature Filters',
|
|
441
|
+
actions: [
|
|
442
|
+
{
|
|
443
|
+
id: 'reset',
|
|
444
|
+
label: 'Reset filters',
|
|
445
|
+
icon: 'M16.023 9.348h4.992M2.985 19.644v-4.992',
|
|
446
|
+
tone: 'neutral',
|
|
447
|
+
},
|
|
448
|
+
],
|
|
449
|
+
},
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
const action = wrapper.get('.mint-collapsible-card__action')
|
|
453
|
+
expect(action.attributes('aria-label')).toBe('Reset filters')
|
|
454
|
+
expect(wrapper.find('.mint-collapsible-card__action-icon').exists()).toBe(true)
|
|
455
|
+
|
|
456
|
+
await action.trigger('click')
|
|
457
|
+
expect(wrapper.emitted('action')).toEqual([['reset']])
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
it('does not emit disabled actions', async () => {
|
|
461
|
+
const wrapper = mount(CollapsibleCard, {
|
|
462
|
+
props: {
|
|
463
|
+
title: 'Feature Filters',
|
|
464
|
+
actions: [{ id: 'reset', label: 'Reset filters', disabled: true }],
|
|
465
|
+
},
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
await wrapper.get('.mint-collapsible-card__action').trigger('click')
|
|
469
|
+
expect(wrapper.emitted('action')).toBeUndefined()
|
|
470
|
+
})
|
|
471
|
+
})
|
|
472
|
+
|
|
426
473
|
describe('content slot', () => {
|
|
427
474
|
it('should render default slot content', () => {
|
|
428
475
|
const wrapper = mount(CollapsibleCard, {
|
|
@@ -3,6 +3,7 @@ import { nextTick } from 'vue'
|
|
|
3
3
|
import { describe, expect, it } from 'vitest'
|
|
4
4
|
import FormBuilder from '../../components/FormBuilder.vue'
|
|
5
5
|
import FormActions from '../../components/FormActions.vue'
|
|
6
|
+
import Tooltip from '../../components/Tooltip.vue'
|
|
6
7
|
import { defineControlModel, useControlWorkspace, type ControlSchema } from '../../composables/useControlSchema'
|
|
7
8
|
import type { FormSchema } from '../../types/form-builder'
|
|
8
9
|
|
|
@@ -286,6 +287,62 @@ describe('FormBuilder', () => {
|
|
|
286
287
|
expect(wrapper.find('.mint-form-actions').exists()).toBe(false)
|
|
287
288
|
})
|
|
288
289
|
|
|
290
|
+
it('renders boolean fields as compact choice rows', async () => {
|
|
291
|
+
const schema: FormSchema = {
|
|
292
|
+
sections: [
|
|
293
|
+
{
|
|
294
|
+
id: 'display',
|
|
295
|
+
title: 'Display',
|
|
296
|
+
fields: [
|
|
297
|
+
{
|
|
298
|
+
name: 'showOutliers',
|
|
299
|
+
label: 'Show outliers',
|
|
300
|
+
type: 'toggle',
|
|
301
|
+
defaultValue: true,
|
|
302
|
+
hint: 'Keep flagged peaks visible',
|
|
303
|
+
icon: ['M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z'],
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: 'includeMetadata',
|
|
307
|
+
label: 'Include metadata',
|
|
308
|
+
type: 'checkbox',
|
|
309
|
+
defaultValue: false,
|
|
310
|
+
hint: 'Add method context',
|
|
311
|
+
icon: ['M12 3c4.418 0 8 1.343 8 3s-3.582 3-8 3-8-1.343-8-3z'],
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const wrapper = mount(FormBuilder, {
|
|
319
|
+
props: { schema, showActions: false },
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
const toggle = wrapper.get('.mint-toggle--row')
|
|
323
|
+
const checkbox = wrapper.get('.mint-checkbox--row')
|
|
324
|
+
|
|
325
|
+
expect(toggle.text()).toContain('Show outliers')
|
|
326
|
+
expect(toggle.text()).not.toContain('Keep flagged peaks visible')
|
|
327
|
+
expect(toggle.find('.mint-toggle__row-svg').exists()).toBe(true)
|
|
328
|
+
expect(wrapper.findAll('.mint-form-field__label')).toHaveLength(0)
|
|
329
|
+
|
|
330
|
+
expect(checkbox.text()).toContain('Include metadata')
|
|
331
|
+
expect(checkbox.text()).not.toContain('Add method context')
|
|
332
|
+
expect(checkbox.find('.mint-checkbox__row-svg').exists()).toBe(true)
|
|
333
|
+
expect(wrapper.findAllComponents(Tooltip).map(tooltip => tooltip.props('text'))).toEqual([
|
|
334
|
+
'Keep flagged peaks visible',
|
|
335
|
+
'Add method context',
|
|
336
|
+
])
|
|
337
|
+
|
|
338
|
+
await toggle.trigger('click')
|
|
339
|
+
|
|
340
|
+
expect(wrapper.emitted('update:modelValue')?.at(-1)?.[0]).toMatchObject({
|
|
341
|
+
showOutliers: false,
|
|
342
|
+
includeMetadata: false,
|
|
343
|
+
})
|
|
344
|
+
})
|
|
345
|
+
|
|
289
346
|
it('renders default actions by default', () => {
|
|
290
347
|
const wrapper = mount(FormBuilder, {
|
|
291
348
|
props: { controls },
|
|
@@ -12,6 +12,7 @@ describe('PluginWorkspaceView shell helpers', () => {
|
|
|
12
12
|
actions: undefined,
|
|
13
13
|
'settings-tab-analysis': undefined,
|
|
14
14
|
header: undefined,
|
|
15
|
+
'collapsed-footer': undefined,
|
|
15
16
|
'section-import': undefined,
|
|
16
17
|
unrelated: undefined,
|
|
17
18
|
}
|
|
@@ -22,6 +23,7 @@ describe('PluginWorkspaceView shell helpers', () => {
|
|
|
22
23
|
])
|
|
23
24
|
expect(getPluginWorkspaceSidebarSlotNames(slots)).toEqual([
|
|
24
25
|
'header',
|
|
26
|
+
'collapsed-footer',
|
|
25
27
|
'section-import',
|
|
26
28
|
])
|
|
27
29
|
})
|
|
@@ -4,6 +4,7 @@ import AppSidebar from './AppSidebar.vue'
|
|
|
4
4
|
import BaseSlider from './BaseSlider.vue'
|
|
5
5
|
import BaseSelect from './BaseSelect.vue'
|
|
6
6
|
import BaseToggle from './BaseToggle.vue'
|
|
7
|
+
import BaseCheckbox from './BaseCheckbox.vue'
|
|
7
8
|
import BaseButton from './BaseButton.vue'
|
|
8
9
|
import BaseInput from './BaseInput.vue'
|
|
9
10
|
import NumberInput from './NumberInput.vue'
|
|
@@ -25,6 +26,10 @@ const icons = {
|
|
|
25
26
|
zap: 'M13 2L3 14h9l-1 8 10-12h-9l1-8',
|
|
26
27
|
route: ['M18 6l-6 6-4-4-6 6'],
|
|
27
28
|
settings: ['M12 2L2 7l10 5 10-5-10-5z', 'M2 17l10 5 10-5', 'M2 12l10 5 10-5'],
|
|
29
|
+
reset: ['M16.023 9.348h4.992v-.001', 'M2.985 19.644v-4.992', 'M4.031 9.865a8.25 8.25 0 0113.803-3.7'],
|
|
30
|
+
eye: ['M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z', 'M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z'],
|
|
31
|
+
chart: ['M3 3v18h18', 'M7 15l3-3 3 2 4-6'],
|
|
32
|
+
database: ['M12 3c4.418 0 8 1.343 8 3s-3.582 3-8 3-8-1.343-8-3 3.582-3 8-3z', 'M4 6v6c0 1.657 3.582 3 8 3s8-1.343 8-3V6', 'M4 12v6c0 1.657 3.582 3 8 3s8-1.343 8-3v-6'],
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
/* --- Sequence builder panels --- */
|
|
@@ -46,6 +51,8 @@ const sequencePanels: Record<string, SidebarToolSection[]> = {
|
|
|
46
51
|
iconColor: '#6366f1',
|
|
47
52
|
iconBg: '#e0e7ff',
|
|
48
53
|
showToggle: true,
|
|
54
|
+
badge: 'on',
|
|
55
|
+
badgeTone: 'success',
|
|
49
56
|
},
|
|
50
57
|
{
|
|
51
58
|
id: 'randomize',
|
|
@@ -55,6 +62,11 @@ const sequencePanels: Record<string, SidebarToolSection[]> = {
|
|
|
55
62
|
iconColor: '#6366f1',
|
|
56
63
|
iconBg: '#e0e7ff',
|
|
57
64
|
showToggle: true,
|
|
65
|
+
badge: 3,
|
|
66
|
+
badgeTone: 'cta',
|
|
67
|
+
actions: [
|
|
68
|
+
{ id: 'reset-randomize', label: 'Reset randomization', icon: icons.reset, tone: 'neutral' },
|
|
69
|
+
],
|
|
58
70
|
defaultOpen: false,
|
|
59
71
|
},
|
|
60
72
|
{
|
|
@@ -128,6 +140,7 @@ const methods = [
|
|
|
128
140
|
]
|
|
129
141
|
const showOutliers = ref(true)
|
|
130
142
|
const logScale = ref(false)
|
|
143
|
+
const includeMetadata = ref(true)
|
|
131
144
|
const polarity = ref('negative')
|
|
132
145
|
const container = ref('vial')
|
|
133
146
|
const expNumber = ref(1)
|
|
@@ -169,6 +182,10 @@ const schemaControls = defineControls({
|
|
|
169
182
|
showOutliers: {
|
|
170
183
|
label: 'Show outliers',
|
|
171
184
|
default: true,
|
|
185
|
+
hint: 'Keep flagged points visible',
|
|
186
|
+
icon: icons.eye,
|
|
187
|
+
iconColor: '#0ea5e9',
|
|
188
|
+
iconBg: '#e0f2fe',
|
|
172
189
|
section: 'display',
|
|
173
190
|
sectionLabel: 'Display',
|
|
174
191
|
sectionSubtitle: 'Result filters',
|
|
@@ -206,7 +223,14 @@ const modelDrivenSidebarModel = defineControlModel({
|
|
|
206
223
|
defaultOpen: false,
|
|
207
224
|
},
|
|
208
225
|
controls: {
|
|
209
|
-
showOutliers:
|
|
226
|
+
showOutliers: {
|
|
227
|
+
label: 'Show outliers',
|
|
228
|
+
default: true,
|
|
229
|
+
hint: 'Keep flagged points visible',
|
|
230
|
+
icon: icons.eye,
|
|
231
|
+
iconColor: '#0ea5e9',
|
|
232
|
+
iconBg: '#e0f2fe',
|
|
233
|
+
},
|
|
210
234
|
minPeakArea: { type: 'number', default: 1000, min: 0 },
|
|
211
235
|
},
|
|
212
236
|
},
|
|
@@ -301,7 +325,15 @@ function initSimpleToolkit() {
|
|
|
301
325
|
</div>
|
|
302
326
|
</template>
|
|
303
327
|
<template #section-method>
|
|
304
|
-
<BaseToggle
|
|
328
|
+
<BaseToggle
|
|
329
|
+
model-value
|
|
330
|
+
variant="row"
|
|
331
|
+
label="Use custom method paths"
|
|
332
|
+
description="Override the default acquisition directory"
|
|
333
|
+
:icon="icons.route"
|
|
334
|
+
icon-color="#8b5cf6"
|
|
335
|
+
icon-bg="#ede9fe"
|
|
336
|
+
/>
|
|
305
337
|
<BaseSelect
|
|
306
338
|
model-value=""
|
|
307
339
|
placeholder="-- Select Method --"
|
|
@@ -445,13 +477,46 @@ function initSimpleToolkit() {
|
|
|
445
477
|
<BaseSelect v-model="method" label="Method" :options="methods" />
|
|
446
478
|
</template>
|
|
447
479
|
<template #section-filters>
|
|
448
|
-
<BaseToggle
|
|
480
|
+
<BaseToggle
|
|
481
|
+
v-model="showOutliers"
|
|
482
|
+
variant="row"
|
|
483
|
+
label="Exclude outliers"
|
|
484
|
+
description="Hide flagged injections from summaries"
|
|
485
|
+
:icon="icons.eye"
|
|
486
|
+
icon-color="#0ea5e9"
|
|
487
|
+
icon-bg="#e0f2fe"
|
|
488
|
+
/>
|
|
449
489
|
</template>
|
|
450
490
|
<template #section-display>
|
|
451
|
-
<BaseToggle
|
|
452
|
-
|
|
491
|
+
<BaseToggle
|
|
492
|
+
v-model="showOutliers"
|
|
493
|
+
variant="row"
|
|
494
|
+
label="Show outliers"
|
|
495
|
+
description="Keep flagged peaks visible"
|
|
496
|
+
:icon="icons.eye"
|
|
497
|
+
icon-color="#0ea5e9"
|
|
498
|
+
icon-bg="#e0f2fe"
|
|
499
|
+
/>
|
|
500
|
+
<BaseToggle
|
|
501
|
+
v-model="logScale"
|
|
502
|
+
variant="row"
|
|
503
|
+
label="Log scale"
|
|
504
|
+
description="Compress high-intensity spread"
|
|
505
|
+
:icon="icons.chart"
|
|
506
|
+
icon-color="#8b5cf6"
|
|
507
|
+
icon-bg="#ede9fe"
|
|
508
|
+
/>
|
|
453
509
|
</template>
|
|
454
510
|
<template #section-export>
|
|
511
|
+
<BaseCheckbox
|
|
512
|
+
v-model="includeMetadata"
|
|
513
|
+
variant="row"
|
|
514
|
+
label="Include metadata"
|
|
515
|
+
description="Add method and sample context"
|
|
516
|
+
:icon="icons.database"
|
|
517
|
+
icon-color="#10b981"
|
|
518
|
+
icon-bg="#d1fae5"
|
|
519
|
+
/>
|
|
455
520
|
<BaseButton size="sm" variant="secondary">Export CSV</BaseButton>
|
|
456
521
|
<BaseButton size="sm" variant="secondary">Export PNG</BaseButton>
|
|
457
522
|
</template>
|
|
@@ -499,7 +564,15 @@ function initSimpleToolkit() {
|
|
|
499
564
|
<BaseSelect v-model="method" label="Method" :options="methods" />
|
|
500
565
|
</template>
|
|
501
566
|
<template #section-filters>
|
|
502
|
-
<BaseToggle
|
|
567
|
+
<BaseToggle
|
|
568
|
+
v-model="showOutliers"
|
|
569
|
+
variant="row"
|
|
570
|
+
label="Exclude outliers"
|
|
571
|
+
description="Hide flagged injections from summaries"
|
|
572
|
+
:icon="icons.eye"
|
|
573
|
+
icon-color="#0ea5e9"
|
|
574
|
+
icon-bg="#e0f2fe"
|
|
575
|
+
/>
|
|
503
576
|
</template>
|
|
504
577
|
<template #footer>
|
|
505
578
|
<BaseButton size="sm" variant="ghost" style="width: 100%;">Reset Defaults</BaseButton>
|
|
@@ -153,6 +153,7 @@ const emit = defineEmits<{
|
|
|
153
153
|
'update:modelValue': [values: Record<string, unknown>]
|
|
154
154
|
'update:values': [values: Record<string, unknown>]
|
|
155
155
|
'update:collapsed': [value: boolean]
|
|
156
|
+
'section-action': [sectionId: string, actionId: string]
|
|
156
157
|
'form-submit': [sectionId: string, values: Record<string, unknown>]
|
|
157
158
|
'form-cancel': [sectionId: string]
|
|
158
159
|
}>()
|
|
@@ -268,6 +269,30 @@ function expandCollapsed() {
|
|
|
268
269
|
collapsedModel.value = false
|
|
269
270
|
}
|
|
270
271
|
|
|
272
|
+
function handleSectionAction(sectionId: string, actionId: string) {
|
|
273
|
+
emit('section-action', sectionId, actionId)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function isSvgIcon(icon?: string | string[]): boolean {
|
|
277
|
+
if (!icon) return false
|
|
278
|
+
return Array.isArray(icon) || icon.startsWith('M') || icon.startsWith('m')
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function sectionInitial(section: SidebarToolSection): string {
|
|
282
|
+
return section.label.trim().charAt(0).toUpperCase() || '?'
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function railIconStyle(section: SidebarToolSection): Record<string, string> {
|
|
286
|
+
return {
|
|
287
|
+
backgroundColor: section.iconBg ?? 'var(--color-primary-soft)',
|
|
288
|
+
color: section.iconColor ?? 'var(--color-primary)',
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function badgeToneClass(section: SidebarToolSection): string {
|
|
293
|
+
return `mint-sidebar__rail-badge--${section.badgeTone ?? 'cta'}`
|
|
294
|
+
}
|
|
295
|
+
|
|
271
296
|
function mergeSidebarPanels(
|
|
272
297
|
generated: Record<string, SidebarToolSection[]>,
|
|
273
298
|
explicit: Record<string, SidebarToolSection[]>,
|
|
@@ -359,10 +384,14 @@ function firstVisibleViewId(panels: Record<string, SidebarToolSection[]>): strin
|
|
|
359
384
|
:icon="section.icon"
|
|
360
385
|
:icon-color="section.iconColor"
|
|
361
386
|
:icon-bg="section.iconBg"
|
|
387
|
+
:badge="section.badge"
|
|
388
|
+
:badge-tone="section.badgeTone"
|
|
389
|
+
:actions="section.actions"
|
|
362
390
|
:dense="dense"
|
|
363
391
|
:default-open="section.defaultOpen !== false"
|
|
364
392
|
:show-toggle="section.showToggle"
|
|
365
393
|
:toggle-value="toggleState[section.id] ?? false"
|
|
394
|
+
@action="handleSectionAction(section.id, $event)"
|
|
366
395
|
@update:toggle-value="emit('update:toggle', section.id, $event)"
|
|
367
396
|
>
|
|
368
397
|
<slot :name="`section-${section.id}`">
|
|
@@ -398,10 +427,53 @@ function firstVisibleViewId(panels: Record<string, SidebarToolSection[]>): strin
|
|
|
398
427
|
/>
|
|
399
428
|
</div>
|
|
400
429
|
|
|
430
|
+
<div v-else class="mint-sidebar__collapsed mint-sidebar__collapsed--default">
|
|
431
|
+
<button
|
|
432
|
+
v-for="section in activeSections"
|
|
433
|
+
:key="section.id"
|
|
434
|
+
type="button"
|
|
435
|
+
class="mint-sidebar__rail-item"
|
|
436
|
+
:aria-label="`Expand ${section.label}`"
|
|
437
|
+
:title="section.label"
|
|
438
|
+
@click="expandCollapsed"
|
|
439
|
+
>
|
|
440
|
+
<span class="mint-sidebar__rail-icon" :style="railIconStyle(section)">
|
|
441
|
+
<svg
|
|
442
|
+
v-if="section.icon && isSvgIcon(section.icon)"
|
|
443
|
+
class="mint-sidebar__rail-svg"
|
|
444
|
+
viewBox="0 0 24 24"
|
|
445
|
+
fill="none"
|
|
446
|
+
stroke="currentColor"
|
|
447
|
+
stroke-width="2"
|
|
448
|
+
stroke-linecap="round"
|
|
449
|
+
stroke-linejoin="round"
|
|
450
|
+
aria-hidden="true"
|
|
451
|
+
>
|
|
452
|
+
<template v-if="Array.isArray(section.icon)">
|
|
453
|
+
<path v-for="(d, i) in section.icon" :key="i" :d="d" />
|
|
454
|
+
</template>
|
|
455
|
+
<path v-else :d="section.icon" />
|
|
456
|
+
</svg>
|
|
457
|
+
<span v-else class="mint-sidebar__rail-text-icon">
|
|
458
|
+
{{ section.icon || sectionInitial(section) }}
|
|
459
|
+
</span>
|
|
460
|
+
</span>
|
|
461
|
+
<span
|
|
462
|
+
v-if="section.badge !== undefined"
|
|
463
|
+
:class="['mint-sidebar__rail-badge', badgeToneClass(section)]"
|
|
464
|
+
>
|
|
465
|
+
{{ section.badge }}
|
|
466
|
+
</span>
|
|
467
|
+
</button>
|
|
468
|
+
</div>
|
|
469
|
+
|
|
401
470
|
<!-- Footer slot -->
|
|
402
471
|
<div v-if="!collapsedModel && $slots.footer" class="mint-sidebar__footer">
|
|
403
472
|
<slot name="footer" />
|
|
404
473
|
</div>
|
|
474
|
+
<div v-else-if="collapsedModel && $slots['collapsed-footer']" class="mint-sidebar__footer mint-sidebar__footer--collapsed">
|
|
475
|
+
<slot name="collapsed-footer" />
|
|
476
|
+
</div>
|
|
405
477
|
</aside>
|
|
406
478
|
</template>
|
|
407
479
|
|
|
@@ -4,6 +4,10 @@ import BaseCheckbox from './BaseCheckbox.vue'
|
|
|
4
4
|
|
|
5
5
|
const sizes = ['sm', 'md', 'lg'] as const
|
|
6
6
|
const checked = ref(true)
|
|
7
|
+
const icons = {
|
|
8
|
+
database: ['M12 3c4.418 0 8 1.343 8 3s-3.582 3-8 3-8-1.343-8-3z', 'M4 6v6c0 1.657 3.582 3 8 3s8-1.343 8-3V6', 'M4 12v6c0 1.657 3.582 3 8 3s8-1.343 8-3v-6'],
|
|
9
|
+
file: ['M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z', 'M14 2v6h6', 'M16 13H8', 'M16 17H8'],
|
|
10
|
+
}
|
|
7
11
|
</script>
|
|
8
12
|
|
|
9
13
|
<template>
|
|
@@ -53,6 +57,29 @@ const checked = ref(true)
|
|
|
53
57
|
</div>
|
|
54
58
|
</Variant>
|
|
55
59
|
|
|
60
|
+
<Variant title="Choice Rows">
|
|
61
|
+
<div style="padding: 2rem; max-width: 360px; display: flex; flex-direction: column; gap: 0.5rem;">
|
|
62
|
+
<BaseCheckbox
|
|
63
|
+
v-model="checked"
|
|
64
|
+
variant="row"
|
|
65
|
+
label="Include metadata"
|
|
66
|
+
description="Add method and sample context"
|
|
67
|
+
:icon="icons.database"
|
|
68
|
+
icon-color="#10b981"
|
|
69
|
+
icon-bg="#d1fae5"
|
|
70
|
+
/>
|
|
71
|
+
<BaseCheckbox
|
|
72
|
+
:model-value="false"
|
|
73
|
+
variant="row"
|
|
74
|
+
label="Attach report"
|
|
75
|
+
description="Include the rendered summary PDF"
|
|
76
|
+
:icon="icons.file"
|
|
77
|
+
icon-color="#6366f1"
|
|
78
|
+
icon-bg="#e0e7ff"
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
</Variant>
|
|
82
|
+
|
|
56
83
|
<Variant title="Disabled States">
|
|
57
84
|
<div style="padding: 2rem; display: flex; flex-direction: column; gap: 1rem;">
|
|
58
85
|
<BaseCheckbox :model-value="false" label="Disabled unchecked" disabled />
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Renders a styled checkbox with an optional label, size variants, and disabled state. */
|
|
3
|
+
import Tooltip from './Tooltip.vue'
|
|
4
|
+
|
|
3
5
|
interface Props {
|
|
4
6
|
modelValue?: boolean
|
|
5
7
|
label?: string
|
|
8
|
+
description?: string
|
|
9
|
+
icon?: string | string[]
|
|
10
|
+
iconColor?: string
|
|
11
|
+
iconBg?: string
|
|
6
12
|
disabled?: boolean
|
|
7
13
|
size?: 'sm' | 'md' | 'lg'
|
|
14
|
+
variant?: 'default' | 'row'
|
|
8
15
|
}
|
|
9
16
|
|
|
10
17
|
const props = withDefaults(defineProps<Props>(), {
|
|
11
18
|
modelValue: false,
|
|
12
19
|
disabled: false,
|
|
13
20
|
size: 'md',
|
|
21
|
+
variant: 'default',
|
|
14
22
|
})
|
|
15
23
|
|
|
16
24
|
const emit = defineEmits<{
|
|
@@ -21,15 +29,69 @@ function handleChange(event: Event) {
|
|
|
21
29
|
const target = event.target as HTMLInputElement
|
|
22
30
|
emit('update:modelValue', target.checked)
|
|
23
31
|
}
|
|
32
|
+
|
|
33
|
+
function isSvgIcon(icon?: string | string[]): boolean {
|
|
34
|
+
if (!icon) return false
|
|
35
|
+
return Array.isArray(icon) || icon.startsWith('M') || icon.startsWith('m')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function iconStyle(): Record<string, string> {
|
|
39
|
+
return {
|
|
40
|
+
color: props.iconColor ?? (props.modelValue ? 'var(--color-primary)' : 'var(--text-muted)'),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
24
43
|
</script>
|
|
25
44
|
|
|
26
45
|
<template>
|
|
27
46
|
<label
|
|
28
47
|
:class="[
|
|
29
48
|
'mint-checkbox',
|
|
49
|
+
`mint-checkbox--${props.variant}`,
|
|
30
50
|
props.disabled ? 'mint-checkbox--disabled' : '',
|
|
51
|
+
props.modelValue ? 'mint-checkbox--checked' : '',
|
|
31
52
|
]"
|
|
32
53
|
>
|
|
54
|
+
<span
|
|
55
|
+
v-if="props.variant === 'row' && props.icon"
|
|
56
|
+
class="mint-checkbox__row-icon"
|
|
57
|
+
:style="iconStyle()"
|
|
58
|
+
aria-hidden="true"
|
|
59
|
+
>
|
|
60
|
+
<svg
|
|
61
|
+
v-if="isSvgIcon(props.icon)"
|
|
62
|
+
class="mint-checkbox__row-svg"
|
|
63
|
+
viewBox="0 0 24 24"
|
|
64
|
+
fill="none"
|
|
65
|
+
stroke="currentColor"
|
|
66
|
+
stroke-width="2"
|
|
67
|
+
stroke-linecap="round"
|
|
68
|
+
stroke-linejoin="round"
|
|
69
|
+
>
|
|
70
|
+
<template v-if="Array.isArray(props.icon)">
|
|
71
|
+
<path v-for="(d, i) in props.icon" :key="i" :d="d" />
|
|
72
|
+
</template>
|
|
73
|
+
<path v-else :d="props.icon" />
|
|
74
|
+
</svg>
|
|
75
|
+
<span v-else class="mint-checkbox__row-text-icon">{{ props.icon }}</span>
|
|
76
|
+
</span>
|
|
77
|
+
<span
|
|
78
|
+
v-if="props.variant === 'row' && props.label"
|
|
79
|
+
class="mint-checkbox__copy"
|
|
80
|
+
>
|
|
81
|
+
<Tooltip
|
|
82
|
+
v-if="props.description"
|
|
83
|
+
:text="props.description"
|
|
84
|
+
position="right"
|
|
85
|
+
max-width="16rem"
|
|
86
|
+
>
|
|
87
|
+
<span :class="['mint-checkbox__label', `mint-checkbox__label--${props.size}`, 'mint-checkbox__label--tooltip']">
|
|
88
|
+
{{ props.label }}
|
|
89
|
+
</span>
|
|
90
|
+
</Tooltip>
|
|
91
|
+
<span v-else :class="['mint-checkbox__label', `mint-checkbox__label--${props.size}`]">
|
|
92
|
+
{{ props.label }}
|
|
93
|
+
</span>
|
|
94
|
+
</span>
|
|
33
95
|
<div class="mint-checkbox__input-wrapper">
|
|
34
96
|
<input
|
|
35
97
|
type="checkbox"
|
|
@@ -58,7 +120,7 @@ function handleChange(event: Event) {
|
|
|
58
120
|
</svg>
|
|
59
121
|
</div>
|
|
60
122
|
</div>
|
|
61
|
-
<span v-if="props.label" :class="['mint-checkbox__label', `mint-checkbox__label--${props.size}`]">
|
|
123
|
+
<span v-if="props.label && props.variant !== 'row'" :class="['mint-checkbox__label', `mint-checkbox__label--${props.size}`]">
|
|
62
124
|
{{ props.label }}
|
|
63
125
|
</span>
|
|
64
126
|
</label>
|
|
@@ -5,6 +5,10 @@ import BaseToggle from './BaseToggle.vue'
|
|
|
5
5
|
const sizes = ['sm', 'md', 'lg'] as const
|
|
6
6
|
const toggleOn = ref(true)
|
|
7
7
|
const toggleOff = ref(false)
|
|
8
|
+
const icons = {
|
|
9
|
+
eye: ['M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z', 'M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z'],
|
|
10
|
+
chart: ['M3 3v18h18', 'M7 15l3-3 3 2 4-6'],
|
|
11
|
+
}
|
|
8
12
|
</script>
|
|
9
13
|
|
|
10
14
|
<template>
|
|
@@ -54,6 +58,29 @@ const toggleOff = ref(false)
|
|
|
54
58
|
</div>
|
|
55
59
|
</Variant>
|
|
56
60
|
|
|
61
|
+
<Variant title="Choice Rows">
|
|
62
|
+
<div style="padding: 2rem; max-width: 360px; display: flex; flex-direction: column; gap: 0.5rem;">
|
|
63
|
+
<BaseToggle
|
|
64
|
+
v-model="toggleOn"
|
|
65
|
+
variant="row"
|
|
66
|
+
label="Show outliers"
|
|
67
|
+
description="Keep flagged peaks visible"
|
|
68
|
+
:icon="icons.eye"
|
|
69
|
+
icon-color="#0ea5e9"
|
|
70
|
+
icon-bg="#e0f2fe"
|
|
71
|
+
/>
|
|
72
|
+
<BaseToggle
|
|
73
|
+
v-model="toggleOff"
|
|
74
|
+
variant="row"
|
|
75
|
+
label="Log scale"
|
|
76
|
+
description="Compress high-intensity spread"
|
|
77
|
+
:icon="icons.chart"
|
|
78
|
+
icon-color="#8b5cf6"
|
|
79
|
+
icon-bg="#ede9fe"
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
</Variant>
|
|
83
|
+
|
|
57
84
|
<Variant title="Without Label">
|
|
58
85
|
<div style="padding: 2rem; display: flex; gap: 1.5rem; align-items: center;">
|
|
59
86
|
<BaseToggle v-model="toggleOn" size="sm" />
|