@morscherlab/mint-sdk 1.4.5 → 1.4.6

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/lab.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { P as time_exports } from "./records-CWRztE5M.js";
2
2
  import { C as FormulaInput_default, L as definePlateEditor, S as useSequenceUtils, a as StepWizard_default, b as MoleculeInput_default, v as ConcentrationInput_default, w as formula_exports, x as SequenceInput_default, y as useConcentrationUnits, z as defineWellPlate } from "./useListReorder-QqJc1dpX.js";
3
- import { C as defineWellPlateControlProps, S as defineDoseDesignControlModel, T as defineWellPlateDoseControlProps, _ as SampleSelector_default, a as useReagentSeries, c as InstrumentStatusPanels_default, d as ChemicalFormula_default, f as AdductText_default, g as SampleHierarchyTree_default, h as useProtocolTemplates, i as generateDilutionSeries, l as InstrumentStateBadge_default, m as ProtocolStepEditor_default, n as DEFAULT_PRESETS, o as ResourceCard_default, p as ScientificNumber_default, r as DEFAULT_UNITS, s as TimeRangeInput_default, u as SequenceProgressBar_default, v as useSampleGroups, w as defineWellPlateDoseComponentBindings, x as defineDoseCalculatorControlProps } from "./lab-CbdDLAVr.js";
3
+ import { C as defineWellPlateControlProps, S as defineDoseDesignControlModel, T as defineWellPlateDoseControlProps, _ as SampleSelector_default, a as useReagentSeries, c as InstrumentStatusPanels_default, d as ChemicalFormula_default, f as AdductText_default, g as SampleHierarchyTree_default, h as useProtocolTemplates, i as generateDilutionSeries, l as InstrumentStateBadge_default, m as ProtocolStepEditor_default, n as DEFAULT_PRESETS, o as ResourceCard_default, p as ScientificNumber_default, r as DEFAULT_UNITS, s as TimeRangeInput_default, u as SequenceProgressBar_default, v as useSampleGroups, w as defineWellPlateDoseComponentBindings, x as defineDoseCalculatorControlProps } from "./lab-C3BmZ2lI.js";
4
4
  import { C as hexToHsl, E as sampleTypeMarker, S as deriveShade, T as WellPlate_default, _ as mintPlotlyTemplate, b as PlateEditor_default, g as ChartContainer_default, h as PlotlyChart_default, n as ScheduleCalendar_default, r as useScheduleDrag, t as useRackEditor, v as DoseCalculator_default, w as hslToHex, y as dose_exports } from "./useRackEditor-BHGM1Ngz.js";
5
5
  export { AdductText_default as AdductText, ChartContainer_default as ChartContainer, ChemicalFormula_default as ChemicalFormula, ConcentrationInput_default as ConcentrationInput, DEFAULT_PRESETS, DEFAULT_UNITS, DoseCalculator_default as DoseCalculator, FormulaInput_default as FormulaInput, InstrumentStateBadge_default as InstrumentStateBadge, InstrumentStatusPanels_default as InstrumentStatusPanels, MoleculeInput_default as MoleculeInput, PlateEditor_default as PlateEditor, PlotlyChart_default as PlotlyChart, ProtocolStepEditor_default as ProtocolStepEditor, ResourceCard_default as ResourceCard, SampleHierarchyTree_default as SampleHierarchyTree, SampleSelector_default as SampleSelector, ScheduleCalendar_default as ScheduleCalendar, ScientificNumber_default as ScientificNumber, SequenceInput_default as SequenceInput, SequenceProgressBar_default as SequenceProgressBar, StepWizard_default as StepWizard, TimeRangeInput_default as TimeRangeInput, WellPlate_default as WellPlate, defineDoseCalculatorControlProps, defineDoseDesignControlModel, definePlateEditor, defineWellPlate, defineWellPlateControlProps, defineWellPlateDoseComponentBindings, defineWellPlateDoseControlProps, deriveShade, dose_exports as dose, formula_exports as formula, generateDilutionSeries, hexToHsl, hslToHex, mintPlotlyTemplate, sampleTypeMarker, time_exports as time, useConcentrationUnits, useProtocolTemplates, useRackEditor, useReagentSeries, useSampleGroups, useScheduleDrag, useSequenceUtils };
package/dist/styles.css CHANGED
@@ -88,17 +88,17 @@
88
88
  text-decoration: none;
89
89
  }
90
90
 
91
- .mint-status-panels__tabs[data-v-3c05755e] { padding: 0 1rem;
91
+ .mint-status-panels__tabs[data-v-1a9246f6] { padding: 0 var(--mint-section-card-inset);
92
92
  }
93
- .mint-status-panels__body[data-v-3c05755e] { padding: 0.75rem 1rem;
93
+ .mint-status-panels__body[data-v-1a9246f6] { padding: 0.75rem var(--mint-section-card-inset);
94
94
  }
95
- .mint-status-panels__bar[data-v-3c05755e] { display: flex; flex-direction: column; gap: 0.5rem;
95
+ .mint-status-panels__bar[data-v-1a9246f6] { display: flex; flex-direction: column; gap: 0.5rem;
96
96
  }
97
- .mint-status-panels__series[data-v-3c05755e] { align-self: flex-end;
97
+ .mint-status-panels__series[data-v-1a9246f6] { align-self: flex-end;
98
98
  }
99
- .mint-status-panels__kv[data-v-3c05755e] { margin: 0; padding: 0.25rem 1rem 0.5rem;
99
+ .mint-status-panels__kv[data-v-1a9246f6] { margin: 0; padding: 0.25rem var(--mint-section-card-inset) 0.5rem;
100
100
  }
101
- .mint-status-panels__kv-row[data-v-3c05755e] {
101
+ .mint-status-panels__kv-row[data-v-1a9246f6] {
102
102
  display: flex;
103
103
  align-items: baseline;
104
104
  justify-content: space-between;
@@ -106,11 +106,11 @@
106
106
  padding: 0.4375rem 0;
107
107
  border-bottom: 1px solid var(--border-light);
108
108
  }
109
- .mint-status-panels__kv-row[data-v-3c05755e]:last-child { border-bottom: 0;
109
+ .mint-status-panels__kv-row[data-v-1a9246f6]:last-child { border-bottom: 0;
110
110
  }
111
- .mint-status-panels__kv-key[data-v-3c05755e] { font-size: 0.75rem; color: var(--text-secondary);
111
+ .mint-status-panels__kv-key[data-v-1a9246f6] { font-size: 0.75rem; color: var(--text-secondary);
112
112
  }
113
- .mint-status-panels__kv-value[data-v-3c05755e] {
113
+ .mint-status-panels__kv-value[data-v-1a9246f6] {
114
114
  margin: 0;
115
115
  min-width: 0;
116
116
  font-family: var(--font-mono);
@@ -443,6 +443,9 @@
443
443
  --mint-gutter: 0.5rem;
444
444
  /* Distance from the outermost cards to the window edge, in every density. */
445
445
  --mint-page-inset: 1rem;
446
+ /* Text inset of a SectionCard: its title, row text and table edge columns
447
+ start here, and page chrome over a card (breadcrumbs) can line up with it. */
448
+ --mint-section-card-inset: 0.75rem;
446
449
 
447
450
  /* Semantic colors */
448
451
  --mint-success: #10B981;
@@ -686,6 +689,7 @@ html.dark [data-palette="forest"] {
686
689
  /* Comfortable density (html[data-density], written by the settings store):
687
690
  the pre-v2 control heights and a wider gutter. */
688
691
  [data-density="comfortable"] {
692
+ --mint-section-card-inset: 0.875rem;
689
693
  --form-height-sm: 2rem;
690
694
  --form-height-md: 2.25rem;
691
695
  --form-height-lg: 3rem;
@@ -6900,6 +6904,11 @@ html.dark {
6900
6904
  min-height: 0;
6901
6905
  }
6902
6906
 
6907
+ /* A collapsed fill card gives its track back to its siblings. */
6908
+ .mint-section-card--fill.mint-section-card--collapsed {
6909
+ flex: none;
6910
+ }
6911
+
6903
6912
  .mint-section-card--fill .mint-section-card__body {
6904
6913
  flex: 1;
6905
6914
  min-height: 0;
@@ -6914,7 +6923,7 @@ html.dark {
6914
6923
  min-height: 2.5rem;
6915
6924
  /* 4px block padding: a 28px header control plus padding plus the 1px rule
6916
6925
  stays inside the 40px strip (6px made it 41px). */
6917
- padding: 0.25rem 0.625rem 0.25rem 0.75rem;
6926
+ padding: 0.25rem 0.625rem 0.25rem var(--mint-section-card-inset);
6918
6927
  border-bottom: 1px solid var(--border-color);
6919
6928
  flex-shrink: 0;
6920
6929
  }
@@ -6923,6 +6932,45 @@ html.dark {
6923
6932
  border-bottom-color: var(--border-light);
6924
6933
  }
6925
6934
 
6935
+ /* A collapsed card is its header alone: no rule under it. */
6936
+ .mint-section-card__header--collapsed {
6937
+ border-bottom-color: transparent;
6938
+ }
6939
+
6940
+ .mint-section-card__toggle {
6941
+ display: inline-flex;
6942
+ align-items: center;
6943
+ gap: var(--mint-section-card-gap);
6944
+ margin: 0;
6945
+ padding: 0;
6946
+ border: 0;
6947
+ background: none;
6948
+ color: inherit;
6949
+ font: inherit;
6950
+ cursor: pointer;
6951
+ }
6952
+
6953
+ .mint-section-card__toggle:focus-visible {
6954
+ outline: none;
6955
+ box-shadow: var(--focus-ring-offset);
6956
+ border-radius: var(--radius-sm);
6957
+ }
6958
+
6959
+ .mint-section-card__chevron {
6960
+ width: 0.875rem;
6961
+ height: 0.875rem;
6962
+ color: var(--text-muted);
6963
+ transition: transform var(--mint-transition);
6964
+ }
6965
+
6966
+ .mint-section-card__toggle[aria-expanded="false"] .mint-section-card__chevron {
6967
+ transform: rotate(-90deg);
6968
+ }
6969
+
6970
+ .mint-section-card__toggle:hover .mint-section-card__chevron {
6971
+ color: var(--text-secondary);
6972
+ }
6973
+
6926
6974
  /* Muted icon, no tinted chip: every accent renders the same. */
6927
6975
  .mint-section-card__icon-chip {
6928
6976
  display: inline-flex;
@@ -6970,7 +7018,8 @@ html.dark {
6970
7018
  display: flex;
6971
7019
  align-items: center;
6972
7020
  justify-content: space-between;
6973
- padding: 0.5rem 1rem;
7021
+ /* Text starts on the card inset; controls end where the header's do. */
7022
+ padding: 0.5rem 0.625rem 0.5rem var(--mint-section-card-inset);
6974
7023
  border-top: 1px solid var(--border-color);
6975
7024
  background: var(--bg-primary);
6976
7025
  font-size: 0.75rem;
@@ -6991,7 +7040,7 @@ html.dark {
6991
7040
  display: flex;
6992
7041
  align-items: center;
6993
7042
  gap: var(--mint-section-card-gap);
6994
- padding: 0.375rem 0.75rem;
7043
+ padding: 0.375rem var(--mint-section-card-inset);
6995
7044
  background: var(--bg-secondary);
6996
7045
  transition: background-color var(--mint-transition);
6997
7046
  }
@@ -7026,14 +7075,28 @@ button.mint-section-card__row:hover {
7026
7075
  /* Comfortable density gives the header and rows back their height. */
7027
7076
  [data-density="comfortable"] .mint-section-card__header {
7028
7077
  min-height: 2.75rem;
7029
- padding: 0.25rem 0.75rem 0.25rem 0.875rem;
7078
+ padding-right: 0.75rem;
7079
+ }
7080
+
7081
+ [data-density="comfortable"] .mint-section-card__footer {
7082
+ padding-right: 0.75rem;
7030
7083
  }
7031
7084
 
7032
7085
  [data-density="comfortable"] .mint-section-card__row {
7033
7086
  padding-top: 0.5rem;
7034
7087
  padding-bottom: 0.5rem;
7035
- /* Same left inset as the header, so row content lines up under the header. */
7036
- padding-left: 0.875rem;
7088
+ }
7089
+
7090
+ /* A table that is the card body's own child starts and ends on the card's text
7091
+ inset, so its first column lines up under the header title. A table inside a
7092
+ padded wrapper keeps its padding, and the checkbox and actions columns stay
7093
+ centred controls. The table's size padding uses !important, so this must too. */
7094
+ .mint-section-card__body > .mint-dataframe :is(.mint-dataframe__th, .mint-dataframe__td):first-child:not(.mint-dataframe__th--checkbox, .mint-dataframe__td--checkbox) {
7095
+ padding-left: var(--mint-section-card-inset) !important;
7096
+ }
7097
+
7098
+ .mint-section-card__body > .mint-dataframe :is(.mint-dataframe__th, .mint-dataframe__td):last-child:not(.mint-dataframe__th--actions, .mint-dataframe__td--actions) {
7099
+ padding-right: var(--mint-section-card-inset) !important;
7037
7100
  }
7038
7101
 
7039
7102
  /* SecretInput — plugin secret settings field with a set / not set / environment status.
@@ -0,0 +1 @@
1
+ export {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morscherlab/mint-sdk",
3
- "version": "1.4.5",
3
+ "version": "1.4.6",
4
4
  "description": "MINT Platform SDK — Vue 3 components, composables, and types for plugin development. MINT = Mass-spec Infrastructure for Next-generation Toolkits.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -51,7 +51,10 @@
51
51
  "types": "./dist/types/index.d.ts",
52
52
  "import": "./dist/types/index.js"
53
53
  },
54
- "./styles": "./dist/styles.css"
54
+ "./styles": {
55
+ "types": "./dist/styles.d.ts",
56
+ "default": "./dist/styles.css"
57
+ }
55
58
  },
56
59
  "files": [
57
60
  "dist",
@@ -178,4 +178,37 @@ describe('SectionCard', () => {
178
178
  expect(withoutFooter.find('.mint-section-card__footer').exists()).toBe(false)
179
179
  })
180
180
  })
181
+
182
+ describe('collapsible', () => {
183
+ it('should hide the body and emit the collapsed state when a collapsible header is clicked', async () => {
184
+ const wrapper = mount(SectionCard, {
185
+ props: { title: 'Alerts', count: 3, collapsible: true },
186
+ slots: { default: '<p class="body-probe">rows</p>', actions: '<button class="action-probe">Export</button>' },
187
+ attachTo: document.body,
188
+ })
189
+ const toggle = wrapper.find('button.mint-section-card__toggle')
190
+ expect(toggle.attributes('aria-expanded')).toBe('true')
191
+ expect(wrapper.find('h2.mint-section-card__title > button').element).toBe(toggle.element)
192
+ expect(toggle.text()).toContain('Alerts')
193
+ expect(toggle.find('.action-probe').exists()).toBe(false)
194
+
195
+ await toggle.trigger('click')
196
+
197
+ expect(toggle.attributes('aria-expanded')).toBe('false')
198
+ expect(wrapper.find('.mint-section-card__body').isVisible()).toBe(false)
199
+ expect(wrapper.emitted('update:collapsed')).toEqual([[true]])
200
+ wrapper.unmount()
201
+ })
202
+
203
+ it('should keep the body visible with no toggle when collapsed is set without collapsible', () => {
204
+ const wrapper = mount(SectionCard, {
205
+ props: { title: 'Alerts', collapsed: true },
206
+ slots: { default: '<p class="body-probe">rows</p>' },
207
+ attachTo: document.body,
208
+ })
209
+ expect(wrapper.find('.mint-section-card__toggle').exists()).toBe(false)
210
+ expect(wrapper.find('.mint-section-card__body').isVisible()).toBe(true)
211
+ wrapper.unmount()
212
+ })
213
+ })
181
214
  })
@@ -105,11 +105,11 @@ function formatValue(value: string | number | boolean | null): string {
105
105
  </template>
106
106
 
107
107
  <style scoped>
108
- .mint-status-panels__tabs { padding: 0 1rem; }
109
- .mint-status-panels__body { padding: 0.75rem 1rem; }
108
+ .mint-status-panels__tabs { padding: 0 var(--mint-section-card-inset); }
109
+ .mint-status-panels__body { padding: 0.75rem var(--mint-section-card-inset); }
110
110
  .mint-status-panels__bar { display: flex; flex-direction: column; gap: 0.5rem; }
111
111
  .mint-status-panels__series { align-self: flex-end; }
112
- .mint-status-panels__kv { margin: 0; padding: 0.25rem 1rem 0.5rem; }
112
+ .mint-status-panels__kv { margin: 0; padding: 0.25rem var(--mint-section-card-inset) 0.5rem; }
113
113
  .mint-status-panels__kv-row {
114
114
  display: flex;
115
115
  align-items: baseline;
@@ -2,7 +2,7 @@
2
2
  /** One-card page region from the MINT 1.1 card system: 12px radius, the
3
3
  * single --card-shadow token, a header strip (icon chip, title, mono count,
4
4
  * actions), and hairline-separated content — no card-in-card. */
5
- import { computed } from 'vue'
5
+ import { computed, useId } from 'vue'
6
6
  import type { SectionCardAccent, SectionCardDivider } from '../types'
7
7
 
8
8
  interface Props {
@@ -23,6 +23,8 @@ interface Props {
23
23
  * definite height (a flex or grid track).
24
24
  */
25
25
  fill?: boolean
26
+ /** The title toggles the body; bind `v-model:collapsed` to control or keep the state. */
27
+ collapsible?: boolean
26
28
  }
27
29
 
28
30
  const props = withDefaults(defineProps<Props>(), {
@@ -32,8 +34,14 @@ const props = withDefaults(defineProps<Props>(), {
32
34
  uppercase: false,
33
35
  divider: 'strong',
34
36
  fill: false,
37
+ collapsible: false,
35
38
  })
36
39
 
40
+ const collapsed = defineModel<boolean>('collapsed', { default: false })
41
+ const bodyId = useId()
42
+ /** `collapsed` hides content only on a card that also shows the toggle to open it again. */
43
+ const hidden = computed(() => props.collapsible && collapsed.value)
44
+
37
45
  const iconPaths = computed(() =>
38
46
  props.icon === undefined ? [] : Array.isArray(props.icon) ? props.icon : [props.icon],
39
47
  )
@@ -41,6 +49,7 @@ const iconPaths = computed(() =>
41
49
  const headerClasses = computed(() => [
42
50
  'mint-section-card__header',
43
51
  props.divider === 'light' ? 'mint-section-card__header--divider-light' : '',
52
+ hidden.value ? 'mint-section-card__header--collapsed' : '',
44
53
  ])
45
54
 
46
55
  const titleClasses = computed(() => [
@@ -51,6 +60,7 @@ const titleClasses = computed(() => [
51
60
  const cardClasses = computed(() => [
52
61
  'mint-section-card',
53
62
  props.fill ? 'mint-section-card--fill' : '',
63
+ hidden.value ? 'mint-section-card--collapsed' : '',
54
64
  ])
55
65
  </script>
56
66
 
@@ -74,20 +84,46 @@ const cardClasses = computed(() => [
74
84
  <path v-for="(d, i) in iconPaths" :key="i" :d="d" />
75
85
  </svg>
76
86
  </span>
77
- <h2 :class="titleClasses">{{ title }}</h2>
78
- <span v-if="count !== undefined" class="mint-section-card__count">{{ count }}</span>
87
+ <!-- Disclosure pattern: the button sits inside the heading, never the reverse. -->
88
+ <h2 :class="titleClasses">
89
+ <button
90
+ v-if="collapsible"
91
+ type="button"
92
+ class="mint-section-card__toggle"
93
+ :aria-expanded="!collapsed"
94
+ :aria-controls="bodyId"
95
+ @click="collapsed = !collapsed"
96
+ >
97
+ {{ title }}
98
+ <span v-if="count !== undefined" class="mint-section-card__count">{{ count }}</span>
99
+ <svg
100
+ class="mint-section-card__chevron"
101
+ viewBox="0 0 24 24"
102
+ fill="none"
103
+ stroke="currentColor"
104
+ stroke-width="2"
105
+ stroke-linecap="round"
106
+ stroke-linejoin="round"
107
+ aria-hidden="true"
108
+ >
109
+ <path d="m6 9 6 6 6-6" />
110
+ </svg>
111
+ </button>
112
+ <template v-else>{{ title }}</template>
113
+ </h2>
114
+ <span v-if="count !== undefined && !collapsible" class="mint-section-card__count">{{ count }}</span>
79
115
  <div class="mint-section-card__spacer"></div>
80
116
  <slot name="actions" />
81
117
  </div>
82
118
  <!-- Controls belong to the card's chrome, so they sit outside the body and
83
119
  stay put while the body scrolls. -->
84
- <div v-if="$slots.toolbar" class="mint-section-card__toolbar">
120
+ <div v-if="$slots.toolbar" v-show="!hidden" class="mint-section-card__toolbar">
85
121
  <slot name="toolbar" />
86
122
  </div>
87
- <div class="mint-section-card__body">
123
+ <div :id="bodyId" v-show="!hidden" class="mint-section-card__body">
88
124
  <slot />
89
125
  </div>
90
- <div v-if="$slots.footer" class="mint-section-card__footer">
126
+ <div v-if="$slots.footer" v-show="!hidden" class="mint-section-card__footer">
91
127
  <slot name="footer" />
92
128
  </div>
93
129
  </section>
@@ -27,6 +27,11 @@
27
27
  min-height: 0;
28
28
  }
29
29
 
30
+ /* A collapsed fill card gives its track back to its siblings. */
31
+ .mint-section-card--fill.mint-section-card--collapsed {
32
+ flex: none;
33
+ }
34
+
30
35
  .mint-section-card--fill .mint-section-card__body {
31
36
  flex: 1;
32
37
  min-height: 0;
@@ -41,7 +46,7 @@
41
46
  min-height: 2.5rem;
42
47
  /* 4px block padding: a 28px header control plus padding plus the 1px rule
43
48
  stays inside the 40px strip (6px made it 41px). */
44
- padding: 0.25rem 0.625rem 0.25rem 0.75rem;
49
+ padding: 0.25rem 0.625rem 0.25rem var(--mint-section-card-inset);
45
50
  border-bottom: 1px solid var(--border-color);
46
51
  flex-shrink: 0;
47
52
  }
@@ -50,6 +55,45 @@
50
55
  border-bottom-color: var(--border-light);
51
56
  }
52
57
 
58
+ /* A collapsed card is its header alone: no rule under it. */
59
+ .mint-section-card__header--collapsed {
60
+ border-bottom-color: transparent;
61
+ }
62
+
63
+ .mint-section-card__toggle {
64
+ display: inline-flex;
65
+ align-items: center;
66
+ gap: var(--mint-section-card-gap);
67
+ margin: 0;
68
+ padding: 0;
69
+ border: 0;
70
+ background: none;
71
+ color: inherit;
72
+ font: inherit;
73
+ cursor: pointer;
74
+ }
75
+
76
+ .mint-section-card__toggle:focus-visible {
77
+ outline: none;
78
+ box-shadow: var(--focus-ring-offset);
79
+ border-radius: var(--radius-sm);
80
+ }
81
+
82
+ .mint-section-card__chevron {
83
+ width: 0.875rem;
84
+ height: 0.875rem;
85
+ color: var(--text-muted);
86
+ transition: transform var(--mint-transition);
87
+ }
88
+
89
+ .mint-section-card__toggle[aria-expanded="false"] .mint-section-card__chevron {
90
+ transform: rotate(-90deg);
91
+ }
92
+
93
+ .mint-section-card__toggle:hover .mint-section-card__chevron {
94
+ color: var(--text-secondary);
95
+ }
96
+
53
97
  /* Muted icon, no tinted chip: every accent renders the same. */
54
98
  .mint-section-card__icon-chip {
55
99
  display: inline-flex;
@@ -97,7 +141,8 @@
97
141
  display: flex;
98
142
  align-items: center;
99
143
  justify-content: space-between;
100
- padding: 0.5rem 1rem;
144
+ /* Text starts on the card inset; controls end where the header's do. */
145
+ padding: 0.5rem 0.625rem 0.5rem var(--mint-section-card-inset);
101
146
  border-top: 1px solid var(--border-color);
102
147
  background: var(--bg-primary);
103
148
  font-size: 0.75rem;
@@ -118,7 +163,7 @@
118
163
  display: flex;
119
164
  align-items: center;
120
165
  gap: var(--mint-section-card-gap);
121
- padding: 0.375rem 0.75rem;
166
+ padding: 0.375rem var(--mint-section-card-inset);
122
167
  background: var(--bg-secondary);
123
168
  transition: background-color var(--mint-transition);
124
169
  }
@@ -153,12 +198,26 @@ button.mint-section-card__row:hover {
153
198
  /* Comfortable density gives the header and rows back their height. */
154
199
  [data-density="comfortable"] .mint-section-card__header {
155
200
  min-height: 2.75rem;
156
- padding: 0.25rem 0.75rem 0.25rem 0.875rem;
201
+ padding-right: 0.75rem;
202
+ }
203
+
204
+ [data-density="comfortable"] .mint-section-card__footer {
205
+ padding-right: 0.75rem;
157
206
  }
158
207
 
159
208
  [data-density="comfortable"] .mint-section-card__row {
160
209
  padding-top: 0.5rem;
161
210
  padding-bottom: 0.5rem;
162
- /* Same left inset as the header, so row content lines up under the header. */
163
- padding-left: 0.875rem;
211
+ }
212
+
213
+ /* A table that is the card body's own child starts and ends on the card's text
214
+ inset, so its first column lines up under the header title. A table inside a
215
+ padded wrapper keeps its padding, and the checkbox and actions columns stay
216
+ centred controls. The table's size padding uses !important, so this must too. */
217
+ .mint-section-card__body > .mint-dataframe :is(.mint-dataframe__th, .mint-dataframe__td):first-child:not(.mint-dataframe__th--checkbox, .mint-dataframe__td--checkbox) {
218
+ padding-left: var(--mint-section-card-inset) !important;
219
+ }
220
+
221
+ .mint-section-card__body > .mint-dataframe :is(.mint-dataframe__th, .mint-dataframe__td):last-child:not(.mint-dataframe__th--actions, .mint-dataframe__td--actions) {
222
+ padding-right: var(--mint-section-card-inset) !important;
164
223
  }
@@ -134,6 +134,9 @@
134
134
  --mint-gutter: 0.5rem;
135
135
  /* Distance from the outermost cards to the window edge, in every density. */
136
136
  --mint-page-inset: 1rem;
137
+ /* Text inset of a SectionCard: its title, row text and table edge columns
138
+ start here, and page chrome over a card (breadcrumbs) can line up with it. */
139
+ --mint-section-card-inset: 0.75rem;
137
140
 
138
141
  /* Semantic colors */
139
142
  --mint-success: #10B981;
@@ -377,6 +380,7 @@ html.dark [data-palette="forest"] {
377
380
  /* Comfortable density (html[data-density], written by the settings store):
378
381
  the pre-v2 control heights and a wider gutter. */
379
382
  [data-density="comfortable"] {
383
+ --mint-section-card-inset: 0.875rem;
380
384
  --form-height-sm: 2rem;
381
385
  --form-height-md: 2.25rem;
382
386
  --form-height-lg: 3rem;