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

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 (165) 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 +56 -4
  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/AppPageSelectorInternal.vue.d.ts} +1 -1
  23. package/dist/components/{AppPillNav.vue.d.ts → internal/AppPillNavInternal.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-BkGF4B4y.js} +4484 -3967
  28. package/dist/components-BkGF4B4y.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-CHsME9H1.js} +40 -28
  40. package/dist/composables-CHsME9H1.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 +3625 -3651
  45. package/dist/templates/componentBindings.d.ts +13 -0
  46. package/dist/templates/index.d.ts +3 -3
  47. package/dist/templates/index.js +2 -2
  48. package/dist/{templates-50NPjaxL.js → templates-B5jmTWuk.js} +111 -56
  49. package/dist/templates-B5jmTWuk.js.map +1 -0
  50. package/dist/types/components.d.ts +6 -25
  51. package/dist/types/index.d.ts +1 -1
  52. package/dist/{useScheduleDrag-D4oWdh41.js → useScheduleDrag-BgzpQT53.js} +160 -117
  53. package/dist/useScheduleDrag-BgzpQT53.js.map +1 -0
  54. package/package.json +1 -1
  55. package/src/__tests__/components/ActionItem.test.ts +6 -6
  56. package/src/__tests__/components/AppLayout.test.ts +44 -0
  57. package/src/__tests__/components/AppPageSelector.test.ts +8 -8
  58. package/src/__tests__/components/AppPillNav.test.ts +53 -6
  59. package/src/__tests__/components/AppSidebar.test.ts +126 -0
  60. package/src/__tests__/components/AppToastContainer.test.ts +0 -11
  61. package/src/__tests__/components/AppTopBar.test.ts +182 -119
  62. package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +7 -1
  63. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +15 -1
  64. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +26 -1
  65. package/src/__tests__/components/CalendarGridPanel.test.ts +3 -3
  66. package/src/__tests__/components/ComponentBindingRenderer.test.ts +161 -0
  67. package/src/__tests__/components/ControlWorkspaceView.test.ts +134 -63
  68. package/src/__tests__/components/DateTimePicker.test.ts +2 -2
  69. package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +185 -0
  70. package/src/__tests__/components/PluginWorkspaceView.test.ts +548 -0
  71. package/src/__tests__/composables/experiment-utils.test.ts +30 -0
  72. package/src/__tests__/composables/useApi.test.ts +30 -0
  73. package/src/__tests__/composables/useAppExperiment.test.ts +100 -1
  74. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +6 -3
  75. package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +6 -6
  76. package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +6 -1
  77. package/src/__tests__/composables/useControlSchema.test.ts +150 -36
  78. package/src/__tests__/composables/usePluginClient.test.ts +99 -2
  79. package/src/__tests__/docs/frontendDocsCatalog.test.ts +120 -25
  80. package/src/__tests__/templates/templates.test.ts +12 -0
  81. package/src/components/AppAvatarMenu.vue +3 -3
  82. package/src/components/AppLayout.story.vue +39 -0
  83. package/src/components/AppLayout.vue +83 -2
  84. package/src/components/AppPluginSwitcher.vue +5 -5
  85. package/src/components/AppSidebar.story.vue +113 -5
  86. package/src/components/AppSidebar.vue +144 -24
  87. package/src/components/AppTopBar.story.vue +2 -5
  88. package/src/components/AppTopBar.vue +35 -425
  89. package/src/components/BioTemplateExperimentWorkspaceView.story.vue +2 -2
  90. package/src/components/BioTemplateExperimentWorkspaceView.vue +6 -0
  91. package/src/components/BioTemplatePackWorkspaceView.story.vue +4 -4
  92. package/src/components/BioTemplatePackWorkspaceView.vue +1 -0
  93. package/src/components/BioTemplatePresetWorkspaceView.story.vue +14 -2
  94. package/src/components/BioTemplatePresetWorkspaceView.vue +11 -2
  95. package/src/components/BioTemplateRenderer.vue +15 -227
  96. package/src/components/ComponentBindingRenderer.story.vue +57 -0
  97. package/src/components/ComponentBindingRenderer.vue +308 -0
  98. package/src/components/ControlWorkspaceView.story.vue +20 -9
  99. package/src/components/ControlWorkspaceView.vue +43 -12
  100. package/src/components/DatePicker.vue +2 -2
  101. package/src/components/DateTimePicker.vue +2 -2
  102. package/src/components/DoseDesignWorkspaceView.story.vue +77 -0
  103. package/src/components/DoseDesignWorkspaceView.vue +255 -0
  104. package/src/components/ExperimentPopover.vue +2 -6
  105. package/src/components/ExperimentSelectorModal.vue +6 -5
  106. package/src/components/FormBuilder.story.vue +190 -0
  107. package/src/components/PluginWorkspaceView.story.vue +334 -0
  108. package/src/components/PluginWorkspaceView.vue +708 -0
  109. package/src/components/SettingsModal.story.vue +87 -0
  110. package/src/components/WellPlate.vue +2 -2
  111. package/src/components/index.ts +3 -12
  112. package/src/components/{AppPageSelector.vue → internal/AppPageSelectorInternal.vue} +9 -9
  113. package/src/components/internal/AppPillNavInternal.vue +194 -0
  114. package/src/components/{CalendarGridPanel.vue → internal/CalendarGridPanelInternal.vue} +1 -1
  115. package/src/components/{WellEditPopup.vue → internal/WellEditPopupInternal.vue} +3 -3
  116. package/src/composables/experiment-utils.ts +26 -0
  117. package/src/composables/index.ts +21 -7
  118. package/src/composables/useApi.ts +9 -2
  119. package/src/composables/useAppExperiment.ts +85 -13
  120. package/src/composables/useBioTemplateComponents.ts +12 -0
  121. package/src/composables/useBioTemplatePackWorkspace.ts +6 -2
  122. package/src/composables/useBioTemplatePresetWorkspace.ts +10 -21
  123. package/src/composables/useBioTemplateWorkspace.ts +6 -4
  124. package/src/composables/useControlSchema.ts +157 -69
  125. package/src/composables/usePluginClient.ts +50 -9
  126. package/src/index.ts +6 -563
  127. package/src/styles/components/app-layout.css +82 -0
  128. package/src/styles/components/app-pill-nav.css +70 -0
  129. package/src/styles/components/app-sidebar.css +119 -0
  130. package/src/styles/components/app-top-bar.css +0 -235
  131. package/src/styles/index.css +0 -1
  132. package/src/templates/componentBindings.ts +38 -0
  133. package/src/templates/index.ts +4 -0
  134. package/src/types/components.ts +6 -31
  135. package/src/types/index.ts +2 -6
  136. package/dist/__tests__/composables/usePluginApi.test.d.ts +0 -13
  137. package/dist/components/FormFieldRenderer.vue.d.ts +0 -28
  138. package/dist/components/FormSection.vue.d.ts +0 -30
  139. package/dist/components/GroupingModal.vue.d.ts +0 -12
  140. package/dist/components/SettingsButton.vue.d.ts +0 -30
  141. package/dist/components/ToastNotification.vue.d.ts +0 -2
  142. package/dist/components-D_Sr0adg.js.map +0 -1
  143. package/dist/composables/usePluginApi.d.ts +0 -22
  144. package/dist/composables-C3dpXQN5.js.map +0 -1
  145. package/dist/templates-50NPjaxL.js.map +0 -1
  146. package/dist/useScheduleDrag-D4oWdh41.js.map +0 -1
  147. package/src/__tests__/components/FormCompatibility.test.ts +0 -94
  148. package/src/__tests__/components/GroupingModal.test.ts +0 -73
  149. package/src/__tests__/components/SettingsButton.test.ts +0 -44
  150. package/src/__tests__/composables/usePluginApi.test.ts +0 -81
  151. package/src/components/AppPillNav.vue +0 -71
  152. package/src/components/FormFieldRenderer.vue +0 -35
  153. package/src/components/FormSection.vue +0 -37
  154. package/src/components/GroupingModal.story.vue +0 -52
  155. package/src/components/GroupingModal.vue +0 -61
  156. package/src/components/SettingsButton.story.vue +0 -58
  157. package/src/components/SettingsButton.vue +0 -64
  158. package/src/components/ToastNotification.vue +0 -9
  159. package/src/composables/usePluginApi.ts +0 -32
  160. package/src/styles/components/settings-button.css +0 -31
  161. /package/dist/__tests__/components/{FormCompatibility.test.d.ts → ComponentBindingRenderer.test.d.ts} +0 -0
  162. /package/dist/__tests__/components/{GroupingModal.test.d.ts → DoseDesignWorkspaceView.test.d.ts} +0 -0
  163. /package/dist/__tests__/components/{SettingsButton.test.d.ts → PluginWorkspaceView.test.d.ts} +0 -0
  164. /package/dist/components/{ActionItem.vue.d.ts → internal/ActionItemInternal.vue.d.ts} +0 -0
  165. /package/src/components/{ActionItem.vue → internal/ActionItemInternal.vue} +0 -0
@@ -1,94 +0,0 @@
1
- import { mount } from '@vue/test-utils'
2
- import { describe, expect, it } from 'vitest'
3
- import FormFieldRenderer from '../../components/FormFieldRenderer.vue'
4
- import FormSection from '../../components/FormSection.vue'
5
- import { useFormBuilder } from '../../composables/useFormBuilder'
6
- import type { FormSchema } from '../../types/form-builder'
7
-
8
- const schema: FormSchema = {
9
- sections: [
10
- {
11
- id: 'parameters',
12
- title: 'Parameters',
13
- fields: [
14
- { name: 'threshold', label: 'Threshold', type: 'text', defaultValue: '0.05' },
15
- ],
16
- },
17
- ],
18
- }
19
-
20
- describe('deprecated form compatibility components', () => {
21
- it('keeps FormFieldRenderer rendering through the internal renderer', () => {
22
- const builder = useFormBuilder(schema)
23
- const field = schema.sections[0].fields[0]
24
-
25
- const wrapper = mount(FormFieldRenderer, {
26
- props: {
27
- field,
28
- resolvedProps: builder.getResolvedFieldProps(field),
29
- form: builder.form,
30
- },
31
- })
32
-
33
- expect(wrapper.text()).toContain('Threshold')
34
- expect(wrapper.find('input').exists()).toBe(true)
35
- })
36
-
37
- it('keeps FormFieldRenderer field slot forwarding', () => {
38
- const builder = useFormBuilder(schema)
39
- const field = schema.sections[0].fields[0]
40
-
41
- const wrapper = mount(FormFieldRenderer, {
42
- props: {
43
- field,
44
- resolvedProps: builder.getResolvedFieldProps(field),
45
- form: builder.form,
46
- },
47
- slots: {
48
- 'field:threshold': '<div class="custom-field">Custom threshold</div>',
49
- },
50
- })
51
-
52
- expect(wrapper.find('.custom-field').text()).toBe('Custom threshold')
53
- expect(wrapper.find('input').exists()).toBe(false)
54
- })
55
-
56
- it('keeps FormSection section and field slot forwarding', () => {
57
- const builder = useFormBuilder(schema)
58
- const section = schema.sections[0]
59
-
60
- const wrapper = mount(FormSection, {
61
- props: {
62
- section,
63
- builder,
64
- },
65
- slots: {
66
- 'field:threshold': '<div class="custom-field">Custom threshold</div>',
67
- 'section:parameters:after': '<div class="section-after">After section</div>',
68
- },
69
- })
70
-
71
- expect(wrapper.text()).toContain('Parameters')
72
- expect(wrapper.find('.custom-field').text()).toBe('Custom threshold')
73
- expect(wrapper.find('.section-after').text()).toBe('After section')
74
- expect(wrapper.find('input').exists()).toBe(false)
75
- })
76
-
77
- it('keeps FormSection whole-section slot forwarding', () => {
78
- const builder = useFormBuilder(schema)
79
- const section = schema.sections[0]
80
-
81
- const wrapper = mount(FormSection, {
82
- props: {
83
- section,
84
- builder,
85
- },
86
- slots: {
87
- 'section:parameters': '<div class="custom-section">Custom section</div>',
88
- },
89
- })
90
-
91
- expect(wrapper.find('.custom-section').text()).toBe('Custom section')
92
- expect(wrapper.text()).not.toContain('Threshold')
93
- })
94
- })
@@ -1,73 +0,0 @@
1
- import { mount } from '@vue/test-utils'
2
- import { describe, expect, it } from 'vitest'
3
- import GroupingModal from '../../components/GroupingModal.vue'
4
- import type { AutoGroupResult } from '../../types/auto-group'
5
-
6
- function mountLegacyModal() {
7
- return mount(GroupingModal, {
8
- props: {
9
- open: true,
10
- samples: ['CTRL_1', 'CTRL_2', 'DRUG_1'],
11
- },
12
- global: {
13
- stubs: {
14
- AutoGroupModal: {
15
- name: 'AutoGroupModal',
16
- props: ['modelValue', 'samples'],
17
- emits: ['update:modelValue', 'apply'],
18
- template: '<div data-testid="auto-group-modal" />',
19
- },
20
- },
21
- },
22
- })
23
- }
24
-
25
- describe('GroupingModal', () => {
26
- it('forwards legacy open and samples props to AutoGroupModal', () => {
27
- const wrapper = mountLegacyModal()
28
- const modal = wrapper.getComponent({ name: 'AutoGroupModal' })
29
-
30
- expect(modal.props('modelValue')).toBe(true)
31
- expect(modal.props('samples')).toEqual(['CTRL_1', 'CTRL_2', 'DRUG_1'])
32
- })
33
-
34
- it('maps AutoGroupResult apply payloads to the legacy mapping format', async () => {
35
- const wrapper = mountLegacyModal()
36
- const modal = wrapper.getComponent({ name: 'AutoGroupModal' })
37
- const result: AutoGroupResult = {
38
- groups: [
39
- { name: 'CTRL', color: '#3B82F6', samples: ['CTRL_1', 'CTRL_2'] },
40
- { name: 'DRUG', color: '#10B981', samples: ['DRUG_1'] },
41
- ],
42
- metadata: [
43
- { sampleName: 'CTRL_1', fields: { Condition: 'CTRL', Replicate: '1' }, group: 'CTRL' },
44
- { sampleName: 'DRUG_1', fields: { Condition: 'DRUG', Replicate: '1' }, group: 'DRUG' },
45
- ],
46
- excludedSamples: [],
47
- }
48
-
49
- await modal.vm.$emit('apply', result)
50
- await modal.vm.$emit('update:modelValue', false)
51
-
52
- expect(wrapper.emitted('apply')).toEqual([
53
- [
54
- {
55
- CTRL: ['CTRL_1', 'CTRL_2'],
56
- DRUG: ['DRUG_1'],
57
- },
58
- ['Condition', 'Replicate'],
59
- ],
60
- ])
61
- expect(wrapper.emitted('close')).toHaveLength(1)
62
- })
63
-
64
- it('emits close when the wrapped modal closes without apply', async () => {
65
- const wrapper = mountLegacyModal()
66
- const modal = wrapper.getComponent({ name: 'AutoGroupModal' })
67
-
68
- await modal.vm.$emit('update:modelValue', false)
69
-
70
- expect(wrapper.emitted('close')).toHaveLength(1)
71
- expect(wrapper.emitted('apply')).toBeUndefined()
72
- })
73
- })
@@ -1,44 +0,0 @@
1
- import { mount } from '@vue/test-utils'
2
- import { afterEach, describe, expect, it } from 'vitest'
3
- import SettingsButton from '../../components/SettingsButton.vue'
4
-
5
- describe('SettingsButton', () => {
6
- afterEach(() => {
7
- document.body.innerHTML = ''
8
- })
9
-
10
- it('toggles the settings dropdown and emits click events', async () => {
11
- const wrapper = mount(SettingsButton, {
12
- slots: {
13
- default: '<div class="settings-content">Settings</div>',
14
- },
15
- attachTo: document.body,
16
- })
17
-
18
- expect(wrapper.find('.mint-settings-dropdown').isVisible()).toBe(false)
19
- expect(wrapper.find('.mint-icon-button').attributes('aria-label')).toBe('Settings')
20
-
21
- await wrapper.find('.mint-settings-button__trigger').trigger('click')
22
-
23
- expect(wrapper.find('.mint-settings-dropdown').isVisible()).toBe(true)
24
- expect(wrapper.emitted('click')).toHaveLength(1)
25
- expect(wrapper.find('.mint-settings-button__trigger').classes()).toContain('mint-icon-button')
26
-
27
- wrapper.unmount()
28
- })
29
-
30
- it('closes on outside click while keeping Escape behavior unchanged', async () => {
31
- const wrapper = mount(SettingsButton, { attachTo: document.body })
32
-
33
- await wrapper.find('.mint-settings-button__trigger').trigger('click')
34
- document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
35
- await wrapper.vm.$nextTick()
36
- expect(wrapper.find('.mint-settings-dropdown').isVisible()).toBe(true)
37
-
38
- document.body.click()
39
- await wrapper.vm.$nextTick()
40
- expect(wrapper.find('.mint-settings-dropdown').isVisible()).toBe(false)
41
-
42
- wrapper.unmount()
43
- })
44
- })
@@ -1,81 +0,0 @@
1
- /**
2
- * Tests for usePluginApi.
3
- *
4
- * Exercises the baseUrl resolution priority that every plugin relies on:
5
- * 1. VITE_API_PREFIX (build-time env override)
6
- * 2. fallbackPrefix option (plugin's route prefix)
7
- * 3. '/api' default
8
- *
9
- * The SDK-wide axios client is a singleton, so these tests stub axios
10
- * with a MockAdapter-style approach via vi.spyOn to observe the
11
- * baseURL actually used at request time.
12
- */
13
-
14
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
15
- import { createPinia, setActivePinia } from 'pinia'
16
- import axios, { type AxiosRequestConfig } from 'axios'
17
-
18
- import { usePluginApi } from '../../composables/usePluginApi'
19
-
20
- // Spy on axios create so each test starts with a fresh instance hook
21
- // and we can observe request configs.
22
- describe('usePluginApi', () => {
23
- let requestConfigs: AxiosRequestConfig[] = []
24
-
25
- beforeEach(() => {
26
- setActivePinia(createPinia())
27
- requestConfigs = []
28
- // Intercept axios.get on the shared instance
29
- vi.spyOn(axios.Axios.prototype, 'get').mockImplementation(async function (
30
- this: unknown,
31
- url: string,
32
- config?: AxiosRequestConfig,
33
- ) {
34
- requestConfigs.push({ url, ...config })
35
- return { data: { called: url, baseURL: config?.baseURL } }
36
- })
37
- })
38
-
39
- afterEach(() => {
40
- vi.restoreAllMocks()
41
- vi.unstubAllEnvs()
42
- })
43
-
44
- it('uses fallbackPrefix when no env override', async () => {
45
- const api = usePluginApi({ fallbackPrefix: '/api/drp' })
46
- await api.get('/sessions')
47
- expect(requestConfigs).toHaveLength(1)
48
- expect(requestConfigs[0]!.baseURL).toBe('/api/drp')
49
- })
50
-
51
- it('falls back to /api when neither env nor fallback provided', async () => {
52
- const api = usePluginApi()
53
- await api.get('/health')
54
- expect(requestConfigs[0]!.baseURL).toBe('/api')
55
- })
56
-
57
- it('VITE_API_PREFIX env var overrides fallbackPrefix', async () => {
58
- vi.stubEnv('VITE_API_PREFIX', '/api/override')
59
- const api = usePluginApi({ fallbackPrefix: '/api/drp' })
60
- await api.get('/check')
61
- expect(requestConfigs[0]!.baseURL).toBe('/api/override')
62
- })
63
-
64
- it('empty VITE_API_PREFIX env var falls through to fallback', async () => {
65
- vi.stubEnv('VITE_API_PREFIX', '')
66
- const api = usePluginApi({ fallbackPrefix: '/api/drp' })
67
- await api.get('/check')
68
- expect(requestConfigs[0]!.baseURL).toBe('/api/drp')
69
- })
70
-
71
- it('returns a client with all CRUD methods', () => {
72
- const api = usePluginApi({ fallbackPrefix: '/api/drp' })
73
- expect(typeof api.get).toBe('function')
74
- expect(typeof api.post).toBe('function')
75
- expect(typeof api.put).toBe('function')
76
- expect(typeof api.patch).toBe('function')
77
- expect(typeof api.delete).toBe('function')
78
- expect(typeof api.upload).toBe('function')
79
- expect(typeof api.buildUrl).toBe('function')
80
- })
81
- })
@@ -1,71 +0,0 @@
1
- <script setup lang="ts">
2
- /** Horizontal pill-style navigation bar that emits select events and supports href, router-link, or button items. */
3
- import { computed } from 'vue'
4
- import type { PillNavItem, PillNavItemInput } from '../types/components'
5
- import { normalizeItemInput } from '../utils/items'
6
- import ActionItem from './ActionItem.vue'
7
-
8
- interface Props {
9
- items: PillNavItemInput[]
10
- currentItemId?: string
11
- }
12
-
13
- const props = defineProps<Props>()
14
-
15
- const emit = defineEmits<{
16
- select: [item: PillNavItem]
17
- }>()
18
-
19
- const normalizedItems = computed<PillNavItem[]>(() => props.items.map(normalizeItemInput))
20
-
21
- function handleClick(item: PillNavItem) {
22
- if (item.disabled) return
23
- if (item.href || item.to) return
24
- emit('select', item)
25
- }
26
-
27
- function isSvgIcon(icon: PillNavItem['icon']): icon is string | string[] {
28
- if (!icon) return false
29
- return Array.isArray(icon) || icon.startsWith('M') || icon.startsWith('m')
30
- }
31
- </script>
32
-
33
- <template>
34
- <nav class="mint-pill-nav" aria-label="Primary">
35
- <ActionItem
36
- v-for="item in normalizedItems"
37
- :key="item.id"
38
- :href="item.href"
39
- :to="item.to"
40
- :disabled="item.disabled"
41
- :class="[
42
- 'mint-pill-nav__item',
43
- { 'mint-pill-nav__item--active': item.id === currentItemId },
44
- { 'mint-pill-nav__item--disabled': item.disabled },
45
- ]"
46
- @click="handleClick(item)"
47
- >
48
- <svg
49
- v-if="isSvgIcon(item.icon)"
50
- class="mint-pill-nav__icon"
51
- viewBox="0 0 24 24"
52
- fill="none"
53
- stroke="currentColor"
54
- stroke-width="2"
55
- stroke-linecap="round"
56
- stroke-linejoin="round"
57
- aria-hidden="true"
58
- >
59
- <template v-if="Array.isArray(item.icon)">
60
- <path v-for="(d, index) in item.icon" :key="index" :d="d" />
61
- </template>
62
- <path v-else :d="item.icon" />
63
- </svg>
64
- {{ item.label }}
65
- </ActionItem>
66
- </nav>
67
- </template>
68
-
69
- <style>
70
- @import '../styles/components/app-pill-nav.css';
71
- </style>
@@ -1,35 +0,0 @@
1
- <script setup lang="ts">
2
- /**
3
- * @deprecated Use FormBuilder with schema/controls and field slots instead.
4
- *
5
- * Compatibility wrapper around the internal FormBuilder field renderer.
6
- */
7
- import type { FormFieldSchema } from '../types/form-builder'
8
- import type { UseFormReturn } from '../composables/useForm'
9
- import FormFieldRendererInternal from './internal/FormFieldRendererInternal.vue'
10
-
11
- interface Props {
12
- field: FormFieldSchema
13
- resolvedProps: Record<string, unknown>
14
- form: UseFormReturn<Record<string, unknown>>
15
- }
16
-
17
- const props = defineProps<Props>()
18
-
19
- defineSlots<{
20
- '`field:${field.name}`'?: (props: Record<string, unknown>) => unknown
21
- [name: string]: ((props: Record<string, unknown>) => unknown) | undefined
22
- }>()
23
- </script>
24
-
25
- <template>
26
- <FormFieldRendererInternal
27
- :field="props.field"
28
- :resolved-props="props.resolvedProps"
29
- :form="props.form"
30
- >
31
- <template #[`field:${props.field.name}`]="slotProps">
32
- <slot :name="`field:${props.field.name}`" v-bind="slotProps" />
33
- </template>
34
- </FormFieldRendererInternal>
35
- </template>
@@ -1,37 +0,0 @@
1
- <script setup lang="ts">
2
- /**
3
- * @deprecated Use FormBuilder with schema/controls and section slots instead.
4
- *
5
- * Compatibility wrapper around the internal FormBuilder section renderer.
6
- */
7
- import type { FormSectionSchema, UseFormBuilderReturn } from '../types/form-builder'
8
- import FormSectionRenderer from './internal/FormSectionRenderer.vue'
9
-
10
- interface Props {
11
- section: FormSectionSchema
12
- builder: UseFormBuilderReturn<Record<string, unknown>>
13
- }
14
-
15
- const props = defineProps<Props>()
16
-
17
- defineSlots<{
18
- '`section:${section.id}`'?: (props: Record<string, unknown>) => unknown
19
- '`field:${field.name}`'?: (props: Record<string, unknown>) => unknown
20
- '`section:${section.id}:after`'?: (props: Record<string, unknown>) => unknown
21
- [name: string]: ((props: Record<string, unknown>) => unknown) | undefined
22
- }>()
23
- </script>
24
-
25
- <template>
26
- <FormSectionRenderer :section="props.section" :builder="props.builder">
27
- <template #[`section:${props.section.id}`]="slotProps">
28
- <slot :name="`section:${props.section.id}`" v-bind="slotProps" />
29
- </template>
30
- <template v-for="field in props.section.fields" :key="field.name" #[`field:${field.name}`]="slotProps">
31
- <slot :name="`field:${field.name}`" v-bind="slotProps" />
32
- </template>
33
- <template #[`section:${props.section.id}:after`]="slotProps">
34
- <slot :name="`section:${props.section.id}:after`" v-bind="slotProps" />
35
- </template>
36
- </FormSectionRenderer>
37
- </template>
@@ -1,52 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref } from 'vue'
3
- import GroupingModal from './GroupingModal.vue'
4
-
5
- const mockSamples = [
6
- 'CTRL_Rep1', 'CTRL_Rep2', 'CTRL_Rep3',
7
- 'TREAT_A_Rep1', 'TREAT_A_Rep2', 'TREAT_A_Rep3',
8
- 'TREAT_B_Rep1', 'TREAT_B_Rep2', 'TREAT_B_Rep3',
9
- ]
10
-
11
- const isOpen = ref(false)
12
- </script>
13
-
14
- <template>
15
- <Story title="Lab/Deprecated/GroupingModal">
16
- <Variant title="Compatibility Preview">
17
- <div style="padding: 2rem;">
18
- <button
19
- type="button"
20
- style="
21
- padding: 0.5rem 1rem;
22
- background: var(--bg-card, #fff);
23
- border: 1px solid var(--border-color, #e5e7eb);
24
- border-radius: 0.375rem;
25
- cursor: pointer;
26
- font-size: 0.875rem;
27
- "
28
- @click="isOpen = true"
29
- >
30
- Open Legacy Grouping Modal
31
- </button>
32
- <GroupingModal
33
- :open="isOpen"
34
- :samples="mockSamples"
35
- @close="isOpen = false"
36
- @apply="(mapping, columns) => { console.log('Applied grouping:', mapping, columns); isOpen = false }"
37
- />
38
- </div>
39
- </Variant>
40
-
41
- <Variant title="Open by Default">
42
- <div style="padding: 2rem;">
43
- <GroupingModal
44
- :open="true"
45
- :samples="mockSamples"
46
- @close="() => {}"
47
- @apply="() => {}"
48
- />
49
- </div>
50
- </Variant>
51
- </Story>
52
- </template>
@@ -1,61 +0,0 @@
1
- <!-- @deprecated Use AutoGroupModal instead -->
2
- <script setup lang="ts">
3
- /** @deprecated Compatibility wrapper for the legacy CSV grouping API. Use AutoGroupModal instead. */
4
- import AutoGroupModal from './AutoGroupModal.vue'
5
- import type { AutoGroupResult } from '../types/auto-group'
6
-
7
- interface Props {
8
- open: boolean
9
- samples: string[]
10
- }
11
-
12
- defineProps<Props>()
13
-
14
- const emit = defineEmits<{
15
- close: []
16
- apply: [mapping: Record<string, string[]>, columns: string[]]
17
- }>()
18
-
19
- let closeEmittedFromApply = false
20
-
21
- function handleApply(result: AutoGroupResult) {
22
- emit('apply', groupsToLegacyMapping(result), metadataColumns(result))
23
- closeEmittedFromApply = true
24
- emit('close')
25
- }
26
-
27
- function handleOpenChange(isOpen: boolean) {
28
- if (isOpen) return
29
- if (closeEmittedFromApply) {
30
- closeEmittedFromApply = false
31
- return
32
- }
33
- emit('close')
34
- }
35
-
36
- function groupsToLegacyMapping(result: AutoGroupResult): Record<string, string[]> {
37
- return Object.fromEntries(result.groups.map(group => [group.name, group.samples]))
38
- }
39
-
40
- function metadataColumns(result: AutoGroupResult): string[] {
41
- const columns: string[] = []
42
- const seen = new Set<string>()
43
- for (const row of result.metadata) {
44
- for (const column of Object.keys(row.fields)) {
45
- if (seen.has(column)) continue
46
- seen.add(column)
47
- columns.push(column)
48
- }
49
- }
50
- return columns
51
- }
52
- </script>
53
-
54
- <template>
55
- <AutoGroupModal
56
- :model-value="open"
57
- :samples="samples"
58
- @update:model-value="handleOpenChange"
59
- @apply="handleApply"
60
- />
61
- </template>
@@ -1,58 +0,0 @@
1
- <script setup lang="ts">
2
- import SettingsButton from './SettingsButton.vue'
3
-
4
- const sizes: ('sm' | 'md' | 'lg')[] = ['sm', 'md', 'lg']
5
- </script>
6
-
7
- <template>
8
- <Story title="Action/Deprecated/SettingsButton">
9
- <Variant title="Playground">
10
- <template #default="{ state }">
11
- <div style="padding: 2rem; display: flex; align-items: center; justify-content: center;">
12
- <SettingsButton :size="state.size">
13
- <div style="padding: 0.75rem; display: flex; flex-direction: column; gap: 0.5rem;">
14
- <label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; color: var(--text-primary, #1e293b);">
15
- <input type="checkbox" checked /> Enable notifications
16
- </label>
17
- <label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; color: var(--text-primary, #1e293b);">
18
- <input type="checkbox" /> Auto-save results
19
- </label>
20
- <label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; color: var(--text-primary, #1e293b);">
21
- <input type="checkbox" checked /> Show grid lines
22
- </label>
23
- </div>
24
- </SettingsButton>
25
- </div>
26
- </template>
27
-
28
- <template #controls="{ state }">
29
- <HstSelect
30
- v-model="state.size"
31
- title="Size"
32
- :options="sizes.map(s => ({ label: s, value: s }))"
33
- />
34
- </template>
35
- </Variant>
36
-
37
- <Variant title="All Sizes">
38
- <div style="padding: 2rem; display: flex; gap: 2rem; align-items: center; justify-content: center;">
39
- <div v-for="size in sizes" :key="size" style="display: flex; flex-direction: column; align-items: center; gap: 0.5rem;">
40
- <SettingsButton :size="size">
41
- <div style="padding: 0.75rem; font-size: 0.875rem; color: var(--text-primary, #1e293b);">
42
- Settings panel content ({{ size }})
43
- </div>
44
- </SettingsButton>
45
- <span style="font-size: 0.75rem; color: var(--text-muted, #94a3b8); text-transform: uppercase; letter-spacing: 0.05em;">
46
- {{ size }}
47
- </span>
48
- </div>
49
- </div>
50
- </Variant>
51
-
52
- <Variant title="Empty (Default Slot)">
53
- <div style="padding: 2rem; display: flex; align-items: center; justify-content: center;">
54
- <SettingsButton />
55
- </div>
56
- </Variant>
57
- </Story>
58
- </template>
@@ -1,64 +0,0 @@
1
- <script setup lang="ts">
2
- /** @deprecated Use AppTopBar settingsConfig or SettingsModal instead. */
3
- import { useDropdownState } from '../composables/useDropdownState'
4
- import IconButton from './IconButton.vue'
5
-
6
- interface Props {
7
- size?: 'sm' | 'md' | 'lg'
8
- }
9
-
10
- withDefaults(defineProps<Props>(), {
11
- size: 'md',
12
- })
13
-
14
- const emit = defineEmits<{
15
- click: [event: MouseEvent]
16
- }>()
17
-
18
- const { isOpen, rootRef, toggle: toggleDropdown } = useDropdownState({
19
- closeOnEscape: false,
20
- })
21
-
22
- function toggle(event: MouseEvent) {
23
- toggleDropdown()
24
- emit('click', event)
25
- }
26
- </script>
27
-
28
- <template>
29
- <div ref="rootRef" class="mint-settings-button">
30
- <IconButton
31
- class="mint-settings-button__trigger"
32
- label="Settings"
33
- variant="ghost"
34
- :size="size"
35
- @click.stop="toggle"
36
- >
37
- <!-- Settings/Gear icon -->
38
- <svg
39
- class="mint-settings-button__icon"
40
- viewBox="0 0 24 24"
41
- fill="none"
42
- stroke="currentColor"
43
- stroke-width="2"
44
- stroke-linecap="round"
45
- stroke-linejoin="round"
46
- >
47
- <path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" />
48
- </svg>
49
- </IconButton>
50
-
51
- <!-- Dropdown panel -->
52
- <div v-show="isOpen" class="mint-settings-dropdown">
53
- <slot>
54
- <div class="mint-settings-dropdown__empty">
55
- No settings configured
56
- </div>
57
- </slot>
58
- </div>
59
- </div>
60
- </template>
61
-
62
- <style>
63
- @import '../styles/components/settings-button.css';
64
- </style>