@morscherlab/mint-sdk 1.0.0-beta.1 → 1.0.0-beta.2

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 (81) hide show
  1. package/dist/__tests__/components/PluginIcon.test.d.ts +1 -0
  2. package/dist/components/AppTopBar.vue.d.ts +2 -0
  3. package/dist/components/BaseButton.vue.d.ts +1 -1
  4. package/dist/components/BaseCheckbox.vue.d.ts +1 -1
  5. package/dist/components/BaseInput.vue.d.ts +1 -1
  6. package/dist/components/BasePill.vue.d.ts +1 -1
  7. package/dist/components/BaseRadioGroup.vue.d.ts +1 -1
  8. package/dist/components/BaseSelect.vue.d.ts +1 -1
  9. package/dist/components/BaseSlider.vue.d.ts +1 -1
  10. package/dist/components/BaseTextarea.vue.d.ts +1 -1
  11. package/dist/components/BaseToggle.vue.d.ts +1 -1
  12. package/dist/components/ColorSlider.vue.d.ts +1 -1
  13. package/dist/components/ConcentrationInput.vue.d.ts +1 -1
  14. package/dist/components/DatePicker.vue.d.ts +1 -1
  15. package/dist/components/DateTimePicker.vue.d.ts +1 -1
  16. package/dist/components/Divider.vue.d.ts +1 -1
  17. package/dist/components/DropdownButton.vue.d.ts +1 -1
  18. package/dist/components/FileUploader.vue.d.ts +1 -1
  19. package/dist/components/FormulaInput.vue.d.ts +1 -1
  20. package/dist/components/IconButton.vue.d.ts +1 -1
  21. package/dist/components/LoadingSpinner.vue.d.ts +1 -1
  22. package/dist/components/MultiSelect.vue.d.ts +1 -1
  23. package/dist/components/NumberInput.vue.d.ts +1 -1
  24. package/dist/components/PluginIcon.vue.d.ts +11 -0
  25. package/dist/components/ProgressBar.vue.d.ts +1 -1
  26. package/dist/components/ReagentEditor.vue.d.ts +1 -1
  27. package/dist/components/ResourceCard.vue.d.ts +1 -1
  28. package/dist/components/SampleSelector.vue.d.ts +1 -1
  29. package/dist/components/ScientificNumber.vue.d.ts +1 -1
  30. package/dist/components/SegmentedControl.vue.d.ts +1 -1
  31. package/dist/components/SettingsModal.vue.d.ts +22 -2
  32. package/dist/components/TagsInput.vue.d.ts +1 -1
  33. package/dist/components/TimePicker.vue.d.ts +1 -1
  34. package/dist/components/TimeRangeInput.vue.d.ts +1 -1
  35. package/dist/components/UnitInput.vue.d.ts +1 -1
  36. package/dist/components/WellPlate.vue.d.ts +1 -1
  37. package/dist/components/index.d.ts +1 -0
  38. package/dist/components/index.js +3 -3
  39. package/dist/{components-CzbQQPCb.js → components-_XqPEhP9.js} +572 -362
  40. package/dist/components-_XqPEhP9.js.map +1 -0
  41. package/dist/composables/index.js +2 -2
  42. package/dist/composables/usePlatformContext.d.ts +3 -0
  43. package/dist/{composables-BXklV5ii.js → composables-tiZqLu1M.js} +2 -2
  44. package/dist/{composables-BXklV5ii.js.map → composables-tiZqLu1M.js.map} +1 -1
  45. package/dist/index.d.ts +2 -2
  46. package/dist/index.js +4 -4
  47. package/dist/install.js +2 -2
  48. package/dist/stores/auth.d.ts +1 -1
  49. package/dist/styles.css +896 -553
  50. package/dist/types/components.d.ts +39 -0
  51. package/dist/types/index.d.ts +1 -1
  52. package/dist/types/platform.d.ts +1 -0
  53. package/dist/{useScheduleDrag-CxBeqYcu.js → useScheduleDrag-CA9sGNJG.js} +4000 -4000
  54. package/dist/useScheduleDrag-CA9sGNJG.js.map +1 -0
  55. package/package.json +1 -1
  56. package/src/__tests__/components/AppTopBar.test.ts +31 -13
  57. package/src/__tests__/components/PluginIcon.test.ts +119 -0
  58. package/src/components/AppTopBar.vue +32 -27
  59. package/src/components/PluginIcon.story.vue +71 -0
  60. package/src/components/PluginIcon.vue +88 -0
  61. package/src/components/SettingsModal.story.vue +337 -45
  62. package/src/components/SettingsModal.vue +251 -64
  63. package/src/components/index.ts +1 -0
  64. package/src/index.ts +4 -0
  65. package/src/styles/components/app-pill-nav.css +1 -2
  66. package/src/styles/components/app-top-bar.css +1 -2
  67. package/src/styles/components/button.css +3 -7
  68. package/src/styles/components/dropdown-button.css +4 -4
  69. package/src/styles/components/input.css +4 -5
  70. package/src/styles/components/number-input.css +3 -3
  71. package/src/styles/components/plugin-icon.css +38 -0
  72. package/src/styles/components/segmented-control.css +4 -7
  73. package/src/styles/components/settings-modal.css +184 -0
  74. package/src/styles/components/tabs.css +1 -2
  75. package/src/styles/components/textarea.css +4 -5
  76. package/src/styles/components/unit-input.css +3 -3
  77. package/src/types/components.ts +42 -0
  78. package/src/types/index.ts +3 -0
  79. package/src/types/platform.ts +1 -0
  80. package/dist/components-CzbQQPCb.js.map +0 -1
  81. package/dist/useScheduleDrag-CxBeqYcu.js.map +0 -1
@@ -1,16 +1,67 @@
1
1
  <script setup lang="ts">
2
- /** Tabbed settings modal with a built-in appearance tab (theme, color palette, table density) and extensible custom tabs. */
3
- import { ref, computed } from 'vue'
2
+ /**
3
+ * Tabbed settings modal with three usage modes:
4
+ *
5
+ * 1. Schema-driven (recommended) — pass `schema` + `v-model:values`. Each
6
+ * group becomes a tab; fields auto-render via the SDK's FormFieldRenderer
7
+ * registry (text, select, number, toggle, molecule, concentration, …).
8
+ * Conditional visibility, validation, and dynamic options come for free.
9
+ *
10
+ * 2. Manual tabs + slots — pass `tabs` and a `<template #tab-{id}>` slot
11
+ * per tab. Use this when you need bespoke widgets the form-builder
12
+ * registry doesn't cover (or for legacy plugins).
13
+ *
14
+ * 3. Appearance only — `showAppearance` (default true) renders the built-in
15
+ * theme / palette / table-density tab. Works alongside both modes above.
16
+ *
17
+ * Layout: `horizontal` (underline tabs, default) or `vertical` (sidebar rail
18
+ * with optional icons + descriptions, recommended for 5+ groups).
19
+ */
20
+ import { ref, computed, watch } from 'vue'
4
21
  import BaseModal from './BaseModal.vue'
22
+ import FormFieldRenderer from './FormFieldRenderer.vue'
23
+ import { useFormBuilder } from '../composables/useFormBuilder'
5
24
  import { useSettingsStore, colorPalettes } from '../stores/settings'
6
- import type { ThemeMode, ColorPalette, TableDensity, SettingsTab } from '../types'
25
+ import type {
26
+ ThemeMode,
27
+ ColorPalette,
28
+ TableDensity,
29
+ SettingsTab,
30
+ SettingsModalLayout,
31
+ SettingsModalSchema,
32
+ FormSchema,
33
+ FormSectionSchema,
34
+ FormEnhancements,
35
+ } from '../types'
36
+
37
+ // Map our settings groups onto the form-builder's flat-section shape.
38
+ // `title: ''` because the rail/pane header already shows the group name.
39
+ function buildFlatSchema(schema: SettingsModalSchema): FormSchema {
40
+ return {
41
+ sections: schema.groups.map<FormSectionSchema>((g) => ({
42
+ id: g.id,
43
+ title: '',
44
+ fields: g.fields,
45
+ columns: g.columns,
46
+ condition: g.condition,
47
+ })),
48
+ }
49
+ }
7
50
 
8
51
  interface Props {
9
52
  modelValue: boolean
10
53
  title?: string
54
+ /** Manual tab descriptors. Ignored when `schema` is set (groups become tabs). */
11
55
  tabs?: SettingsTab[]
12
56
  showAppearance?: boolean
13
57
  size?: 'md' | 'lg' | 'xl'
58
+ layout?: SettingsModalLayout
59
+ /** Declarative schema — fields auto-render via SDK form components. */
60
+ schema?: SettingsModalSchema
61
+ /** Two-way bound values when `schema` is set. */
62
+ values?: Record<string, unknown>
63
+ /** Optional dynamic enhancements (validators, dynamic options, callbacks). */
64
+ enhancements?: FormEnhancements<Record<string, unknown>>
14
65
  }
15
66
 
16
67
  const props = withDefaults(defineProps<Props>(), {
@@ -18,22 +69,85 @@ const props = withDefaults(defineProps<Props>(), {
18
69
  tabs: () => [],
19
70
  showAppearance: true,
20
71
  size: 'lg',
72
+ layout: 'horizontal',
21
73
  })
22
74
 
23
75
  const emit = defineEmits<{
24
76
  'update:modelValue': [value: boolean]
77
+ 'update:values': [data: Record<string, unknown>]
25
78
  close: []
26
79
  }>()
27
80
 
28
81
  const settings = useSettingsStore()
29
82
 
30
- const allTabs = computed<SettingsTab[]>(() =>
31
- props.showAppearance
32
- ? [...props.tabs, { id: 'appearance', label: 'Appearance' }]
83
+ const APPEARANCE_TAB_ID = 'appearance'
84
+
85
+ const APPEARANCE_TAB: SettingsTab = {
86
+ id: APPEARANCE_TAB_ID,
87
+ label: 'Appearance',
88
+ description: 'Theme, color palette, and table density',
89
+ }
90
+
91
+ const isVertical = computed(() => props.layout === 'vertical')
92
+
93
+ // Per-instance prefix for ARIA tab/tabpanel id wiring. Stable across renders.
94
+ const uid = `mint-settings-${Math.random().toString(36).slice(2, 9)}`
95
+ const tabId = (id: string) => `${uid}-tab-${id}`
96
+ const panelId = (id: string) => `${uid}-panel-${id}`
97
+
98
+ // `props.schema` is read once at setup. Pass a stable schema object (declare it
99
+ // at module scope, not inside a render function) — the form-builder is not
100
+ // re-initialized when the prop changes. To swap schemas, key the modal:
101
+ // `<SettingsModal :key="schemaId" :schema="schemaA" />`.
102
+ const builder = props.schema
103
+ ? useFormBuilder(buildFlatSchema(props.schema), props.values, props.enhancements)
104
+ : null
105
+
106
+ if (builder) {
107
+ watch(
108
+ () => ({ ...builder.form.data }),
109
+ (data) => emit('update:values', data as Record<string, unknown>),
110
+ { deep: true },
111
+ )
112
+ }
113
+
114
+ // Schema groups whose `condition` evaluates false against the current data are
115
+ // dropped from the rail entirely — same semantics as section visibility in
116
+ // FormBuilder. Manual `tabs` have no condition mechanism, so they pass through.
117
+ const visibleSchemaGroups = computed(() =>
118
+ builder && props.schema
119
+ ? props.schema.groups.filter((g) => builder.isSectionVisible(g.id))
120
+ : [],
121
+ )
122
+
123
+ const allTabs = computed<SettingsTab[]>(() => {
124
+ const base: SettingsTab[] = props.schema
125
+ ? visibleSchemaGroups.value.map((g) => ({ id: g.id, label: g.label, icon: g.icon, description: g.description }))
33
126
  : props.tabs
127
+ return props.showAppearance ? [...base, APPEARANCE_TAB] : base
128
+ })
129
+
130
+ const activeTab = ref(allTabs.value[0]?.id || APPEARANCE_TAB_ID)
131
+
132
+ const activeTabMeta = computed<SettingsTab | undefined>(() =>
133
+ allTabs.value.find((t) => t.id === activeTab.value),
134
+ )
135
+
136
+ // If the active tab vanishes (group hidden by condition), drop back to the first available.
137
+ watch(allTabs, (tabs) => {
138
+ if (!tabs.some((t) => t.id === activeTab.value)) {
139
+ activeTab.value = tabs[0]?.id ?? APPEARANCE_TAB_ID
140
+ }
141
+ })
142
+
143
+ const activeGroup = computed(() =>
144
+ visibleSchemaGroups.value.find((g) => g.id === activeTab.value),
34
145
  )
35
146
 
36
- const activeTab = ref(allTabs.value[0]?.id || 'appearance')
147
+ const activeGroupVisibleFields = computed(() => {
148
+ if (!activeGroup.value || !builder) return []
149
+ return activeGroup.value.fields.filter((f) => builder.isFieldVisible(f.name))
150
+ })
37
151
 
38
152
  const themeOptions: { value: ThemeMode; label: string }[] = [
39
153
  { value: 'light', label: 'Light' },
@@ -61,9 +175,11 @@ function handleClose() {
61
175
  @update:model-value="emit('update:modelValue', $event)"
62
176
  @close="handleClose"
63
177
  >
64
- <div class="mint-settings-modal">
65
- <!-- Tabs -->
66
- <div v-if="allTabs.length > 1" class="mint-settings-modal__tabs">
178
+ <div :class="['mint-settings-modal', `mint-settings-modal--${layout}`]">
179
+ <div
180
+ v-if="!isVertical && allTabs.length > 1"
181
+ class="mint-settings-modal__tabs"
182
+ >
67
183
  <button
68
184
  v-for="tab in allTabs"
69
185
  :key="tab.id"
@@ -75,68 +191,139 @@ function handleClose() {
75
191
  </button>
76
192
  </div>
77
193
 
78
- <!-- Custom tab content -->
79
- <div class="mint-settings-modal__content">
80
- <template v-for="tab in tabs" :key="tab.id">
81
- <div v-show="activeTab === tab.id">
82
- <slot :name="`tab-${tab.id}`" />
194
+ <div
195
+ v-else-if="isVertical"
196
+ class="mint-settings-modal__rail"
197
+ role="tablist"
198
+ aria-orientation="vertical"
199
+ aria-label="Settings sections"
200
+ >
201
+ <button
202
+ v-for="tab in allTabs"
203
+ :id="tabId(tab.id)"
204
+ :key="tab.id"
205
+ type="button"
206
+ role="tab"
207
+ :class="['mint-settings-modal__rail-item', { 'mint-settings-modal__rail-item--active': activeTab === tab.id }]"
208
+ :aria-selected="activeTab === tab.id"
209
+ :aria-controls="panelId(tab.id)"
210
+ :tabindex="activeTab === tab.id ? 0 : -1"
211
+ @click="activeTab = tab.id"
212
+ >
213
+ <span class="mint-settings-modal__rail-item-icon" aria-hidden="true">
214
+ <span v-if="tab.icon" v-html="tab.icon" />
215
+ </span>
216
+ <span class="mint-settings-modal__rail-item-text">
217
+ <span class="mint-settings-modal__rail-item-label">{{ tab.label }}</span>
218
+ <span v-if="tab.description" class="mint-settings-modal__rail-item-description">
219
+ {{ tab.description }}
220
+ </span>
221
+ </span>
222
+ </button>
223
+ </div>
224
+
225
+ <!-- Vertical mode wraps content in a tabpanel <section> with a header bar; horizontal mode stays a plain <div>. -->
226
+ <component
227
+ :is="isVertical ? 'section' : 'div'"
228
+ :class="isVertical ? 'mint-settings-modal__pane' : 'mint-settings-modal__content'"
229
+ :id="isVertical && activeTabMeta ? panelId(activeTabMeta.id) : undefined"
230
+ :role="isVertical ? 'tabpanel' : undefined"
231
+ :aria-labelledby="isVertical && activeTabMeta ? tabId(activeTabMeta.id) : undefined"
232
+ :tabindex="isVertical ? 0 : undefined"
233
+ >
234
+ <header
235
+ v-if="isVertical && activeTabMeta?.label"
236
+ class="mint-settings-modal__pane-header"
237
+ >
238
+ <h4 class="mint-settings-modal__pane-title">{{ activeTabMeta.label }}</h4>
239
+ <p v-if="activeTabMeta.description" class="mint-settings-modal__pane-subtitle">
240
+ {{ activeTabMeta.description }}
241
+ </p>
242
+ </header>
243
+
244
+ <div :class="isVertical ? 'mint-settings-modal__pane-body' : null">
245
+ <div
246
+ v-if="schema && builder && activeGroup"
247
+ class="mint-settings-modal__group-grid"
248
+ :style="{ '--mint-settings-cols': activeGroup.columns ?? 1 }"
249
+ >
250
+ <template v-for="field in activeGroupVisibleFields" :key="field.name">
251
+ <div :style="field.colSpan ? { gridColumn: `span ${field.colSpan}` } : undefined">
252
+ <slot
253
+ :name="`field:${field.name}`"
254
+ :field="field"
255
+ :form="builder.form"
256
+ :field-props="builder.form.getFieldProps(field.name)"
257
+ >
258
+ <FormFieldRenderer
259
+ :field="field"
260
+ :resolved-props="builder.getResolvedFieldProps(field)"
261
+ :form="builder.form"
262
+ />
263
+ </slot>
264
+ </div>
265
+ </template>
83
266
  </div>
84
- </template>
85
-
86
- <!-- Built-in appearance tab -->
87
- <div v-if="showAppearance" v-show="activeTab === 'appearance'">
88
- <!-- Theme -->
89
- <div class="mint-settings-modal__section">
90
- <div class="mint-settings-modal__section-label">Theme</div>
91
- <div class="mint-settings-modal__option-group">
92
- <button
93
- v-for="opt in themeOptions"
94
- :key="opt.value"
95
- type="button"
96
- :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.theme === opt.value }]"
97
- @click="settings.theme = opt.value"
98
- >
99
- {{ opt.label }}
100
- </button>
267
+
268
+ <template v-else-if="!builder">
269
+ <template v-for="tab in tabs" :key="tab.id">
270
+ <div v-show="activeTab === tab.id">
271
+ <slot :name="`tab-${tab.id}`" />
272
+ </div>
273
+ </template>
274
+ </template>
275
+
276
+ <div v-if="showAppearance" v-show="activeTab === APPEARANCE_TAB_ID">
277
+ <div class="mint-settings-modal__section">
278
+ <div class="mint-settings-modal__section-label">Theme</div>
279
+ <div class="mint-settings-modal__option-group">
280
+ <button
281
+ v-for="opt in themeOptions"
282
+ :key="opt.value"
283
+ type="button"
284
+ :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.theme === opt.value }]"
285
+ @click="settings.theme = opt.value"
286
+ >
287
+ {{ opt.label }}
288
+ </button>
289
+ </div>
101
290
  </div>
102
- </div>
103
291
 
104
- <!-- Color palette -->
105
- <div class="mint-settings-modal__section">
106
- <div class="mint-settings-modal__section-label">Color Palette</div>
107
- <div class="mint-settings-modal__option-group">
108
- <button
109
- v-for="(palette, key) in colorPalettes"
110
- :key="key"
111
- type="button"
112
- :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.colorPalette === key }]"
113
- @click="settings.colorPalette = key as ColorPalette"
114
- >
115
- {{ palette.name }}
116
- </button>
292
+ <div class="mint-settings-modal__section">
293
+ <div class="mint-settings-modal__section-label">Color Palette</div>
294
+ <div class="mint-settings-modal__option-group">
295
+ <button
296
+ v-for="(palette, key) in colorPalettes"
297
+ :key="key"
298
+ type="button"
299
+ :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.colorPalette === key }]"
300
+ @click="settings.colorPalette = key as ColorPalette"
301
+ >
302
+ {{ palette.name }}
303
+ </button>
304
+ </div>
117
305
  </div>
118
- </div>
119
306
 
120
- <!-- Table density -->
121
- <div class="mint-settings-modal__section">
122
- <div class="mint-settings-modal__section-label">Table Density</div>
123
- <div class="mint-settings-modal__option-group">
124
- <button
125
- v-for="opt in densityOptions"
126
- :key="opt.value"
127
- type="button"
128
- :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.tableDensity === opt.value }]"
129
- @click="settings.tableDensity = opt.value"
130
- >
131
- {{ opt.label }}
132
- </button>
307
+ <div class="mint-settings-modal__section">
308
+ <div class="mint-settings-modal__section-label">Table Density</div>
309
+ <div class="mint-settings-modal__option-group">
310
+ <button
311
+ v-for="opt in densityOptions"
312
+ :key="opt.value"
313
+ type="button"
314
+ :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.tableDensity === opt.value }]"
315
+ @click="settings.tableDensity = opt.value"
316
+ >
317
+ {{ opt.label }}
318
+ </button>
319
+ </div>
320
+ <p class="mint-settings-modal__note">Adjusts row height in data tables.</p>
133
321
  </div>
134
- <p class="mint-settings-modal__note">Adjusts row height in data tables.</p>
135
- </div>
136
322
 
137
- <slot name="appearance" />
323
+ <slot name="appearance" />
324
+ </div>
138
325
  </div>
139
- </div>
326
+ </component>
140
327
  </div>
141
328
  </BaseModal>
142
329
  </template>
@@ -45,6 +45,7 @@ export { default as AppPluginSwitcher } from './AppPluginSwitcher.vue'
45
45
  export { default as AppSidebar } from './AppSidebar.vue'
46
46
  export { default as AppLayout } from './AppLayout.vue'
47
47
  export { default as AppContainer } from './AppContainer.vue'
48
+ export { default as PluginIcon } from './PluginIcon.vue'
48
49
 
49
50
  // Utility components
50
51
  export { default as Skeleton } from './Skeleton.vue'
package/src/index.ts CHANGED
@@ -38,6 +38,7 @@ export {
38
38
  AppSidebar,
39
39
  AppLayout,
40
40
  AppContainer,
41
+ PluginIcon,
41
42
  Skeleton,
42
43
  // Biological experiment components
43
44
  WellPlate,
@@ -301,6 +302,9 @@ export type {
301
302
  ConfirmVariant,
302
303
  // SettingsModal types
303
304
  SettingsTab,
305
+ SettingsModalLayout,
306
+ SettingsGroup,
307
+ SettingsModalSchema,
304
308
  // ScientificNumber types
305
309
  NumberNotation,
306
310
  // TimePicker types
@@ -13,8 +13,7 @@
13
13
  display: inline-flex;
14
14
  align-items: center;
15
15
  border: 0;
16
- /* 1px optical centering offset for 13px text. See CLAUDE.md § Optical Centering. */
17
- padding: 0.3125rem 0.875rem 0.4375rem;
16
+ padding: 0.375rem 0.875rem;
18
17
  border-radius: 9999px;
19
18
  font-size: 0.8125rem;
20
19
  font-weight: 500;
@@ -278,8 +278,7 @@
278
278
  display: inline-flex;
279
279
  align-items: center;
280
280
  gap: 0.25rem;
281
- /* Asymmetric top/bottom padding Fira Sans optical centering. */
282
- padding: 0.3125rem 0.875rem 0.4375rem;
281
+ padding: 0.375rem 0.875rem;
283
282
  border: 0;
284
283
  background: transparent;
285
284
  border-radius: 9999px;
@@ -133,24 +133,20 @@
133
133
  }
134
134
 
135
135
  /* Sizes */
136
- /* Optical centering: Fira Sans' cap-letter midpoint sits above the em-box center.
137
- Old rule (pre-0.16) compensated with a 2px upward shift; user testing showed
138
- that read as "too high" across sizes. New rule: 1px upward shift universally.
139
- See CLAUDE.md § Optical Centering. */
140
136
  .mint-button--sm {
141
- padding: 0.3125rem 0.75rem 0.4375rem;
137
+ padding: 0.375rem 0.75rem;
142
138
  font-size: 0.875rem;
143
139
  min-height: var(--form-height-sm);
144
140
  }
145
141
 
146
142
  .mint-button--md {
147
- padding: 0.4375rem 1rem 0.5625rem;
143
+ padding: 0.5rem 1rem;
148
144
  font-size: 0.875rem;
149
145
  min-height: var(--form-height-md);
150
146
  }
151
147
 
152
148
  .mint-button--lg {
153
- padding: 0.6875rem 1.5rem 0.8125rem;
149
+ padding: 0.75rem 1.5rem;
154
150
  font-size: 1rem;
155
151
  min-height: var(--form-height-lg);
156
152
  }
@@ -125,21 +125,21 @@
125
125
  background-color: var(--bg-tertiary);
126
126
  }
127
127
 
128
- /* Trigger sizes — 1px optical padding shift (0.16+). See CLAUDE.md § Optical Centering. */
128
+ /* Trigger sizes */
129
129
  .mint-dropdown-button__trigger--sm {
130
- padding: 0.3125rem 0.75rem 0.4375rem;
130
+ padding: 0.375rem 0.75rem;
131
131
  font-size: 0.875rem;
132
132
  min-height: var(--form-height-sm);
133
133
  }
134
134
 
135
135
  .mint-dropdown-button__trigger--md {
136
- padding: 0.4375rem 1rem 0.5625rem;
136
+ padding: 0.5rem 1rem;
137
137
  font-size: 0.875rem;
138
138
  min-height: var(--form-height-md);
139
139
  }
140
140
 
141
141
  .mint-dropdown-button__trigger--lg {
142
- padding: 0.6875rem 1.5rem 0.8125rem;
142
+ padding: 0.75rem 1.5rem;
143
143
  font-size: 1rem;
144
144
  min-height: var(--form-height-lg);
145
145
  }
@@ -45,22 +45,21 @@
45
45
  cursor: not-allowed;
46
46
  }
47
47
 
48
- /* Size variants — 1px optical padding shift (0.16+).
49
- See CLAUDE.md § Optical Centering. */
48
+ /* Size variants */
50
49
  .mint-input--sm {
51
- padding: 0.3125rem 0.625rem 0.4375rem;
50
+ padding: 0.375rem 0.625rem;
52
51
  font-size: 0.8125rem;
53
52
  min-height: var(--form-height-sm);
54
53
  }
55
54
 
56
55
  .mint-input--md {
57
- padding: 0.4375rem 0.75rem 0.5625rem;
56
+ padding: 0.5rem 0.75rem;
58
57
  font-size: 0.875rem;
59
58
  min-height: var(--form-height-md);
60
59
  }
61
60
 
62
61
  .mint-input--lg {
63
- padding: 0.6875rem 1rem 0.8125rem;
62
+ padding: 0.75rem 1rem;
64
63
  font-size: 1rem;
65
64
  min-height: var(--form-height-lg);
66
65
  }
@@ -72,9 +72,9 @@
72
72
 
73
73
  /* Horizontal padding matches BaseInput canonical scale (10/12/16) so FormField's
74
74
  12px label indent aligns with the numeric text across all input types. */
75
- .mint-number-input__input--sm { padding: 0.3125rem 0.625rem 0.4375rem; font-size: 0.875rem; }
76
- .mint-number-input__input--md { padding: 0.4375rem 0.75rem 0.5625rem; font-size: 0.875rem; }
77
- .mint-number-input__input--lg { padding: 0.6875rem 1rem 0.8125rem; font-size: 1rem; }
75
+ .mint-number-input__input--sm { padding: 0.375rem 0.625rem; font-size: 0.875rem; }
76
+ .mint-number-input__input--md { padding: 0.5rem 0.75rem; font-size: 0.875rem; }
77
+ .mint-number-input__input--lg { padding: 0.75rem 1rem; font-size: 1rem; }
78
78
 
79
79
  /* ---------- Unit suffix ---------- */
80
80
 
@@ -0,0 +1,38 @@
1
+ /* PluginIcon — BEM. Renders a plugin's icon inside a sized chip with two
2
+ visual variants. Tintable via `--mint-plugin-icon-tone` custom property. */
3
+
4
+ .mint-plugin-icon {
5
+ display: inline-flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ flex-shrink: 0;
9
+ border-radius: var(--radius-md, 0.5rem);
10
+ overflow: hidden;
11
+ }
12
+
13
+ /* Sizes */
14
+ .mint-plugin-icon--sm { width: 1.5rem; height: 1.5rem; }
15
+ .mint-plugin-icon--md { width: 2rem; height: 2rem; }
16
+ .mint-plugin-icon--lg { width: 3rem; height: 3rem; border-radius: 0.75rem; }
17
+
18
+ /* Variants — `--mint-plugin-icon-tone` falls back to --color-primary */
19
+ .mint-plugin-icon--solid {
20
+ background-color: var(--mint-plugin-icon-tone, var(--color-primary));
21
+ color: white;
22
+ }
23
+ .mint-plugin-icon--tinted {
24
+ background-color: color-mix(in srgb, var(--mint-plugin-icon-tone, var(--color-primary)) 12%, transparent);
25
+ color: var(--mint-plugin-icon-tone, var(--color-primary));
26
+ }
27
+
28
+ /* Inner svg/img — sized to ~62% of chip */
29
+ .mint-plugin-icon--sm .mint-plugin-icon__svg,
30
+ .mint-plugin-icon--sm .mint-plugin-icon__img { width: 0.9375rem; height: 0.9375rem; }
31
+ .mint-plugin-icon--md .mint-plugin-icon__svg,
32
+ .mint-plugin-icon--md .mint-plugin-icon__img { width: 1.125rem; height: 1.125rem; }
33
+ .mint-plugin-icon--lg .mint-plugin-icon__svg,
34
+ .mint-plugin-icon--lg .mint-plugin-icon__img { width: 1.5rem; height: 1.5rem; }
35
+
36
+ .mint-plugin-icon__img {
37
+ object-fit: contain;
38
+ }
@@ -114,24 +114,21 @@
114
114
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
115
115
  }
116
116
 
117
- /* Size variants - simple — 1px optical padding shift universally (0.16+).
118
- See CLAUDE.md § Optical Centering. */
117
+ /* Size variants - simple */
119
118
  .mint-segmented-control__option--simple.mint-segmented-control__option--sm {
120
- padding: 0.3125rem 0.75rem 0.4375rem;
119
+ padding: 0.375rem 0.75rem;
121
120
  font-size: 0.75rem;
122
121
  min-height: var(--form-height-sm);
123
122
  }
124
123
 
125
124
  .mint-segmented-control__option--simple.mint-segmented-control__option--md {
126
- padding: 0.4375rem 1rem 0.5625rem;
125
+ padding: 0.5rem 1rem;
127
126
  font-size: 0.875rem;
128
127
  min-height: var(--form-height-md);
129
128
  }
130
129
 
131
130
  .mint-segmented-control__option--simple.mint-segmented-control__option--lg {
132
- /* 9/11 (not 11/13 like BaseButton lg) — SegmentedControl uses a smaller
133
- em-box at 1rem font + 1.25 line-height, so the 1px shift lands here. */
134
- padding: 0.5625rem 1.25rem 0.6875rem;
131
+ padding: 0.625rem 1.25rem;
135
132
  font-size: 1rem;
136
133
  min-height: var(--form-height-lg);
137
134
  }