@med-o/ui 0.0.0-stage → 0.3.0

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -2
  3. package/dist/index.d.ts +2237 -0
  4. package/dist/index.js +5119 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/styles.css +1 -0
  7. package/package.json +77 -4
  8. package/src/accordion/accordion.css +184 -0
  9. package/src/alert/alert.css +147 -0
  10. package/src/avatar/avatar.css +221 -0
  11. package/src/badge/badge.css +153 -0
  12. package/src/bottom-sheet/bottom-sheet.css +202 -0
  13. package/src/breadcrumbs/breadcrumbs.css +147 -0
  14. package/src/bulk-action-bar/bulk-action-bar.css +98 -0
  15. package/src/button/button.css +319 -0
  16. package/src/card/card.css +217 -0
  17. package/src/checkbox/choice.css +196 -0
  18. package/src/combobox/combobox.css +125 -0
  19. package/src/date-time/date-time.css +474 -0
  20. package/src/dialog/dialog.css +319 -0
  21. package/src/drawer/drawer.css +156 -0
  22. package/src/empty-state/empty-state.css +161 -0
  23. package/src/field/field.css +115 -0
  24. package/src/link/link.css +103 -0
  25. package/src/list/list.css +172 -0
  26. package/src/list-view/list-view.css +473 -0
  27. package/src/listbox/listbox.css +203 -0
  28. package/src/media/media.css +298 -0
  29. package/src/menu/menu.css +183 -0
  30. package/src/navigation/navigation.css +625 -0
  31. package/src/pagination/pagination.css +132 -0
  32. package/src/popover/popover.css +166 -0
  33. package/src/progress/progress.css +225 -0
  34. package/src/search/search.css +309 -0
  35. package/src/select/select.css +229 -0
  36. package/src/slider/slider.css +212 -0
  37. package/src/styles.css +48 -0
  38. package/src/switch/switch.css +160 -0
  39. package/src/table/table.css +286 -0
  40. package/src/tabs/tabs.css +211 -0
  41. package/src/tag/tag.css +225 -0
  42. package/src/text-field/input.css +206 -0
  43. package/src/toast/toast.css +184 -0
  44. package/src/toggle-button/toggle-button.css +143 -0
  45. package/src/tooltip/tooltip.css +66 -0
  46. package/src/upload/upload.css +380 -0
  47. package/src/utils/utilities.css +14 -0
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Breadcrumbs – med.o Design System
3
+ *
4
+ * <nav class="medo-breadcrumbs medo-breadcrumbs--md" aria-label="Breadcrumb">
5
+ * <ol class="medo-breadcrumbs__list">
6
+ * <li class="medo-breadcrumbs__item"><a class="medo-breadcrumbs__icon-part" aria-label="Home">⌂</a> ›</li>
7
+ * <li class="medo-breadcrumbs__item"><a class="medo-breadcrumbs__link"><span class="medo-breadcrumbs__label">Projects</span></a> ›</li>
8
+ * <li class="medo-breadcrumbs__item"><span class="medo-breadcrumbs__link medo-breadcrumbs__current" aria-current="page">…</span></li>
9
+ * </ol>
10
+ * </nav>
11
+ * sm: caption, current label/sm, height 24, icons 16, separator 12, gap 2 · md: body/sm, label/md, 28, 20, 16, gap 4.
12
+ * Parts: padding space/1, radius/sm, gap icon–text space/0-5. Labels max 200 px with “…”.
13
+ * State contract: [data-hovered] [data-pressed] [data-focus-visible] [data-current] (React Aria).
14
+ */
15
+
16
+ .medo-breadcrumbs {
17
+ --_height: var(--medo-space-6); /* sm 24 */
18
+ --_icon: var(--medo-size-icon-sm);
19
+ --_sep: var(--medo-space-3);
20
+ --_gap: var(--medo-space-0-5);
21
+ --_text: var(--medo-typography-caption);
22
+ --_current: var(--medo-typography-label-sm);
23
+ }
24
+
25
+ .medo-breadcrumbs--md {
26
+ --_height: calc(var(--medo-space-6) + var(--medo-space-1)); /* 28 */
27
+ --_icon: var(--medo-size-icon-md);
28
+ --_sep: var(--medo-space-4);
29
+ --_gap: var(--medo-space-1);
30
+ --_text: var(--medo-typography-body-sm);
31
+ --_current: var(--medo-typography-label-md);
32
+ }
33
+
34
+ .medo-breadcrumbs__list {
35
+ display: flex;
36
+ flex-wrap: wrap;
37
+ align-items: center;
38
+ gap: var(--_gap);
39
+ margin: 0;
40
+ padding: 0;
41
+ list-style: none;
42
+ }
43
+
44
+ .medo-breadcrumbs__item {
45
+ display: inline-flex;
46
+ align-items: center;
47
+ gap: var(--_gap);
48
+ min-inline-size: 0;
49
+ }
50
+
51
+ .medo-breadcrumbs__link,
52
+ .medo-breadcrumbs__icon-part {
53
+ box-sizing: border-box;
54
+ display: inline-flex;
55
+ align-items: center;
56
+ gap: var(--medo-space-0-5);
57
+ min-block-size: var(--_height);
58
+ padding: var(--medo-space-1);
59
+ border: 0;
60
+ border-radius: var(--medo-radius-sm);
61
+ background: none;
62
+ color: var(--medo-color-text-secondary);
63
+ font: var(--_text);
64
+ text-decoration: none;
65
+ outline: none;
66
+ -webkit-tap-highlight-color: transparent;
67
+ transition:
68
+ background-color var(--medo-motion-duration-fast) var(--medo-motion-easing-standard),
69
+ color var(--medo-motion-duration-fast) var(--medo-motion-easing-standard);
70
+ }
71
+
72
+ .medo-breadcrumbs__link[data-pressed] {
73
+ transition-duration: 0s;
74
+ }
75
+
76
+ .medo-breadcrumbs__link[data-hovered]:not([data-current]) {
77
+ color: var(--medo-color-text-primary);
78
+ text-decoration: underline;
79
+ text-underline-offset: 0.2em;
80
+ }
81
+
82
+ .medo-breadcrumbs__link[data-pressed] {
83
+ background-color: var(--medo-color-state-pressed);
84
+ color: var(--medo-color-text-primary);
85
+ }
86
+
87
+ .medo-breadcrumbs__current {
88
+ color: var(--medo-color-text-primary);
89
+ font: var(--_current);
90
+ cursor: default;
91
+ }
92
+
93
+ .medo-breadcrumbs__label {
94
+ overflow: hidden;
95
+ max-inline-size: 12.5rem; /* 200 */
96
+ text-overflow: ellipsis;
97
+ white-space: nowrap;
98
+ }
99
+
100
+ /* Home and “…”: square, a soft surface on hover so the target is visible. */
101
+ .medo-breadcrumbs__icon-part {
102
+ justify-content: center;
103
+ inline-size: var(--_height);
104
+ padding: 0;
105
+ cursor: pointer;
106
+ }
107
+
108
+ .medo-breadcrumbs__icon-part > svg {
109
+ inline-size: var(--_icon);
110
+ block-size: var(--_icon);
111
+ }
112
+
113
+ .medo-breadcrumbs__icon-part:is([data-hovered], [aria-expanded='true']) {
114
+ background-color: var(--medo-color-state-hover);
115
+ color: var(--medo-color-text-primary);
116
+ }
117
+
118
+ .medo-breadcrumbs__icon-part[data-pressed] {
119
+ background-color: var(--medo-color-state-pressed);
120
+ }
121
+
122
+ .medo-breadcrumbs__link[data-focus-visible],
123
+ .medo-breadcrumbs__icon-part[data-focus-visible] {
124
+ box-shadow: inset 0 0 0 var(--medo-focus-ring-width) var(--medo-color-focus-ring);
125
+ }
126
+
127
+ .medo-breadcrumbs__separator {
128
+ flex: none;
129
+ inline-size: var(--_sep);
130
+ block-size: var(--_sep);
131
+ color: var(--medo-color-text-secondary);
132
+ }
133
+
134
+ /* Mobile: one link to the parent page. */
135
+ .medo-breadcrumbs__back-icon {
136
+ flex: none;
137
+ inline-size: var(--_icon);
138
+ block-size: var(--_icon);
139
+ }
140
+
141
+ @media (forced-colors: active) {
142
+ .medo-breadcrumbs__link[data-focus-visible],
143
+ .medo-breadcrumbs__icon-part[data-focus-visible] {
144
+ outline: 2px solid Highlight;
145
+ outline-offset: -2px;
146
+ }
147
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Bulk action bar – med.o Design System
3
+ *
4
+ * <div class="medo-bulk-bar medo-bulk-bar--desktop" role="toolbar" aria-label="Bulk actions">
5
+ * <span class="medo-bulk-bar__count">3 selected</span> | <button class="medo-toast__button medo-toast__action">Export</button> … | ×
6
+ * </div>
7
+ * Desktop: floating, bottom centre, 24 px above the viewport edge · surface/inverse · radius/lg · elevation/3 ·
8
+ * padding 8 8 8 16 · gap 8 · buttons = _Toast button (32).
9
+ * Mobile: full width at the bottom · surface/raised + top border · Button ghost lg · Icon Button ghost lg · safe area.
10
+ * In: slides up with spring/smooth (a fade with reduced motion).
11
+ */
12
+
13
+ .medo-bulk-bar-host {
14
+ display: contents;
15
+ }
16
+
17
+ .medo-bulk-bar {
18
+ position: fixed;
19
+ z-index: var(--medo-layer-toast);
20
+ box-sizing: border-box;
21
+ display: flex;
22
+ align-items: center;
23
+ /* Slides up with a smooth spring when the first item is selected. */
24
+ transition:
25
+ opacity var(--medo-motion-duration-normal) var(--medo-motion-easing-enter),
26
+ translate var(--medo-motion-spring-smooth-duration) var(--medo-motion-spring-smooth);
27
+ }
28
+ @starting-style {
29
+ .medo-bulk-bar {
30
+ opacity: 0;
31
+ translate: 0 var(--medo-space-4);
32
+ }
33
+ }
34
+
35
+ .medo-bulk-bar--desktop {
36
+ inset-block-end: var(--medo-space-6);
37
+ inset-inline: 0;
38
+ inline-size: fit-content;
39
+ max-inline-size: calc(100vw - 2 * var(--medo-space-4));
40
+ margin-inline: auto;
41
+ gap: var(--medo-space-2);
42
+ padding: var(--medo-space-2) var(--medo-space-2) var(--medo-space-2) var(--medo-space-4);
43
+ border-radius: var(--medo-radius-lg);
44
+ background-color: var(--medo-color-surface-inverse);
45
+ box-shadow: var(--medo-shadow-elevation-3);
46
+ color: var(--medo-color-text-inverse);
47
+ }
48
+
49
+ .medo-bulk-bar__count {
50
+ font: var(--medo-typography-label-md-strong);
51
+ font-variant-numeric: tabular-nums;
52
+ white-space: nowrap;
53
+ }
54
+
55
+ .medo-bulk-bar__divider {
56
+ flex: none;
57
+ inline-size: var(--medo-border-width-thin);
58
+ block-size: var(--medo-space-5);
59
+ background-color: var(--medo-color-text-inverse-secondary);
60
+ opacity: var(--medo-opacity-40);
61
+ }
62
+
63
+ .medo-bulk-bar--mobile {
64
+ inset-inline: 0;
65
+ inset-block-end: 0;
66
+ justify-content: space-between;
67
+ gap: var(--medo-space-1);
68
+ padding: var(--medo-space-2) var(--medo-space-2) max(var(--medo-space-2), env(safe-area-inset-bottom));
69
+ border-block-start: var(--medo-border-width-thin) solid var(--medo-color-border-default);
70
+ background-color: var(--medo-color-surface-raised);
71
+ box-shadow: var(--medo-shadow-elevation-3);
72
+ color: var(--medo-color-text-primary);
73
+ }
74
+
75
+ .medo-bulk-bar__start,
76
+ .medo-bulk-bar__actions {
77
+ display: flex;
78
+ align-items: center;
79
+ gap: var(--medo-space-1);
80
+ min-inline-size: 0;
81
+ }
82
+
83
+ @media (prefers-reduced-motion: reduce) {
84
+ .medo-bulk-bar {
85
+ transition: opacity var(--medo-motion-duration-fast) var(--medo-motion-easing-standard);
86
+ }
87
+ @starting-style {
88
+ .medo-bulk-bar {
89
+ translate: none;
90
+ }
91
+ }
92
+ }
93
+
94
+ @media (forced-colors: active) {
95
+ .medo-bulk-bar {
96
+ border: 1px solid CanvasText;
97
+ }
98
+ }
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Button – med.o Design System
3
+ *
4
+ * Plain CSS on top of the design tokens. The same classes work in React, Vue,
5
+ * Angular or plain HTML:
6
+ * <button class="medo-button medo-button--primary medo-button--md">Save</button>
7
+ *
8
+ * Variants: primary · secondary · ghost · danger · text · ghost-danger
9
+ * Sizes: sm (32) · md (40) · lg (48)
10
+ * Icon only: add medo-button--icon-only, always with aria-label.
11
+ *
12
+ * State contract: [data-hovered] [data-pressed] [data-focus-visible] [data-disabled] [data-pending]
13
+ * (set by React Aria – other frameworks set the same). Plain HTML buttons without
14
+ * data-rac fall back to :hover, :active, :focus-visible and :disabled.
15
+ */
16
+
17
+ .medo-button {
18
+ /* Private custom properties set by the variants. */
19
+ --_bg: transparent;
20
+ --_bg-hover: var(--_bg);
21
+ --_bg-pressed: var(--_bg-hover);
22
+ --_fg: var(--medo-color-text-primary);
23
+ --_fg-hover: var(--_fg);
24
+ --_fg-pressed: var(--_fg-hover);
25
+ --_focus: var(--medo-shadow-focus-ring-inset);
26
+
27
+ box-sizing: border-box;
28
+ display: inline-flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ gap: var(--medo-space-2);
32
+ block-size: var(--medo-size-control-md);
33
+ padding-block: 0;
34
+ padding-inline: var(--medo-space-4);
35
+ border: 0;
36
+ border-radius: var(--medo-radius-md);
37
+ background-color: var(--_bg);
38
+ color: var(--_fg);
39
+ font: var(--medo-typography-label-md);
40
+ letter-spacing: var(--medo-font-tracking-normal);
41
+ text-decoration: none;
42
+ white-space: nowrap;
43
+ cursor: pointer;
44
+ user-select: none;
45
+ -webkit-tap-highlight-color: transparent;
46
+ transition:
47
+ background-color var(--medo-motion-duration-fast) var(--medo-motion-easing-standard),
48
+ color var(--medo-motion-duration-fast) var(--medo-motion-easing-standard),
49
+ box-shadow var(--medo-motion-duration-fast) var(--medo-motion-easing-standard);
50
+ }
51
+
52
+ .medo-button[data-hovered] {
53
+ background-color: var(--_bg-hover);
54
+ color: var(--_fg-hover);
55
+ }
56
+
57
+ @media (hover: hover) {
58
+ .medo-button:not([data-rac]):hover {
59
+ background-color: var(--_bg-hover);
60
+ color: var(--_fg-hover);
61
+ }
62
+ }
63
+
64
+ .medo-button[data-pressed],
65
+ .medo-button:not([data-rac]):active {
66
+ background-color: var(--_bg-pressed);
67
+ color: var(--_fg-pressed);
68
+ }
69
+
70
+ .medo-button:focus-visible {
71
+ outline: none;
72
+ }
73
+
74
+ .medo-button[data-focus-visible],
75
+ .medo-button:not([data-rac]):focus-visible {
76
+ box-shadow: var(--_focus);
77
+ }
78
+
79
+ /* High contrast mode of the operating system (forced-colors) hides box-shadows: fall back to a real outline. */
80
+ @media (forced-colors: active) {
81
+ .medo-button:is([data-focus-visible], :not([data-rac]):focus-visible) {
82
+ outline: var(--medo-focus-ring-width) solid CanvasText;
83
+ outline-offset: 2px;
84
+ }
85
+ }
86
+
87
+ /* Disabled: never only by opacity, the colors keep the label readable for the layout. */
88
+ .medo-button:disabled,
89
+ .medo-button[data-disabled],
90
+ .medo-button[aria-disabled='true']:not([data-pending]) {
91
+ --_bg: var(--medo-color-action-disabled);
92
+ --_bg-hover: var(--_bg);
93
+ --_bg-pressed: var(--_bg);
94
+ --_fg: var(--medo-color-text-disabled);
95
+ --_fg-hover: var(--_fg);
96
+ cursor: not-allowed;
97
+ }
98
+
99
+ /* Pressed shows at once and fades out on release, so a tap feels immediate. Color fades stay with reduced motion. */
100
+ .medo-button:is([data-pressed], :not([data-rac]):active) {
101
+ transition-duration: 0s;
102
+ }
103
+
104
+ /* ---------- Sizes ---------- */
105
+
106
+ .medo-button--sm {
107
+ gap: var(--medo-space-1);
108
+ block-size: var(--medo-size-control-sm);
109
+ padding-inline: var(--medo-space-3);
110
+ }
111
+
112
+ .medo-button--lg {
113
+ block-size: var(--medo-size-control-lg);
114
+ padding-inline: var(--medo-space-5);
115
+ font: var(--medo-typography-label-lg);
116
+ }
117
+
118
+ .medo-button__icon {
119
+ display: inline-flex;
120
+ flex-shrink: 0;
121
+ inline-size: var(--medo-size-icon-sm);
122
+ block-size: var(--medo-size-icon-sm);
123
+ }
124
+
125
+ .medo-button--lg .medo-button__icon {
126
+ inline-size: var(--medo-size-icon-md);
127
+ block-size: var(--medo-size-icon-md);
128
+ }
129
+
130
+ .medo-button__icon > svg,
131
+ .medo-button__icon > .medo-spinner {
132
+ inline-size: 100%;
133
+ block-size: 100%;
134
+ }
135
+
136
+ /* Pending (isPending): keeps its look and size; the spinner replaces the icon, the label says what happens. */
137
+ .medo-button[data-pending] {
138
+ cursor: progress;
139
+ }
140
+
141
+ /* ---------- Variants ---------- */
142
+
143
+ .medo-button--primary {
144
+ --_bg: var(--medo-color-action-primary);
145
+ --_bg-hover: var(--medo-color-action-primary-hover);
146
+ --_bg-pressed: var(--medo-color-action-primary-pressed);
147
+ --_fg: var(--medo-color-text-on-action);
148
+ }
149
+
150
+ .medo-button--secondary {
151
+ --_bg: var(--medo-color-action-secondary);
152
+ --_bg-hover: var(--medo-color-action-secondary-hover);
153
+ --_bg-pressed: var(--medo-color-action-secondary-pressed);
154
+ --_fg: var(--medo-color-text-primary);
155
+ }
156
+
157
+ .medo-button--danger {
158
+ --_bg: var(--medo-color-action-danger);
159
+ --_bg-hover: var(--medo-color-action-danger-hover);
160
+ --_bg-pressed: var(--medo-color-action-danger-pressed);
161
+ --_fg: var(--medo-color-text-on-danger);
162
+ }
163
+
164
+ .medo-button--ghost {
165
+ --_bg-hover: var(--medo-color-state-hover);
166
+ --_bg-pressed: var(--medo-color-state-pressed);
167
+ --_fg: var(--medo-color-text-primary);
168
+ --_focus: inset 0 0 0 var(--medo-focus-ring-width) var(--medo-color-focus-ring);
169
+ }
170
+
171
+ .medo-button--ghost-danger {
172
+ --_bg-hover: var(--medo-color-feedback-danger-surface);
173
+ --_bg-pressed: var(--medo-color-state-pressed);
174
+ --_fg: var(--medo-color-text-danger);
175
+ --_focus: inset 0 0 0 var(--medo-focus-ring-width) var(--medo-color-focus-ring);
176
+ }
177
+
178
+ .medo-button--text {
179
+ --_fg: var(--medo-color-text-brand);
180
+ --_fg-hover: var(--medo-color-text-brand-hover);
181
+ --_fg-pressed: var(--medo-color-text-brand-pressed);
182
+ padding-inline: 0;
183
+ border-radius: var(--medo-radius-sm);
184
+ }
185
+
186
+ .medo-button--text[data-hovered],
187
+ .medo-button--text[data-pressed],
188
+ .medo-button--text:not([data-rac]):is(:hover, :active) {
189
+ text-decoration: underline;
190
+ text-underline-offset: 0.2em;
191
+ }
192
+
193
+ .medo-button--text:is([data-focus-visible], :not([data-rac]):focus-visible) {
194
+ box-shadow: none;
195
+ outline: var(--medo-focus-ring-width) solid var(--medo-color-focus-ring);
196
+ outline-offset: 2px;
197
+ }
198
+
199
+ /* Ghost, ghost danger and text buttons have no fill when disabled. */
200
+ .medo-button--ghost:is(:disabled, [data-disabled], [aria-disabled='true']),
201
+ .medo-button--ghost-danger:is(:disabled, [data-disabled], [aria-disabled='true']),
202
+ .medo-button--text:is(:disabled, [data-disabled], [aria-disabled='true']) {
203
+ --_bg: transparent;
204
+ text-decoration: none;
205
+ }
206
+
207
+ /* ---------- Icon only ---------- */
208
+
209
+ .medo-button--icon-only {
210
+ inline-size: var(--medo-size-control-md);
211
+ padding-inline: 0;
212
+ }
213
+
214
+ .medo-button--icon-only .medo-button__icon {
215
+ inline-size: var(--medo-size-icon-md);
216
+ block-size: var(--medo-size-icon-md);
217
+ }
218
+
219
+ .medo-button--icon-only.medo-button--sm {
220
+ inline-size: var(--medo-size-control-sm);
221
+ }
222
+
223
+ .medo-button--icon-only.medo-button--sm .medo-button__icon {
224
+ inline-size: var(--medo-size-icon-sm);
225
+ block-size: var(--medo-size-icon-sm);
226
+ }
227
+
228
+ .medo-button--icon-only.medo-button--lg {
229
+ inline-size: var(--medo-size-control-lg);
230
+ }
231
+
232
+ .medo-button--icon-only.medo-button--lg .medo-button__icon {
233
+ inline-size: var(--medo-size-icon-lg);
234
+ block-size: var(--medo-size-icon-lg);
235
+ }
236
+
237
+ /* ---------- Media icon button ----------
238
+ * On images and video: media (overlay/media circle) · media-ghost (no fill, on a gradient bar).
239
+ * Hover/pressed tint the fill with state/hover-on-media · state/pressed-on-media (same in both themes).
240
+ * Focus: media 2 px ring outside · media-ghost 2 px ring inside. sm is 28 with a 44 px hit area.
241
+ */
242
+
243
+ .medo-button--media,
244
+ .medo-button--media-ghost {
245
+ --_fg: var(--medo-color-text-on-media);
246
+ --_tint: transparent;
247
+ position: relative;
248
+ border-radius: var(--medo-radius-full);
249
+ background-image: linear-gradient(var(--_tint), var(--_tint));
250
+ }
251
+
252
+ .medo-button--media {
253
+ --_bg: var(--medo-color-overlay-media);
254
+ --_focus: var(--medo-shadow-focus-ring);
255
+ }
256
+
257
+ .medo-button--media:is([data-hovered], :not([data-rac]):hover),
258
+ .medo-button--media-ghost:is([data-hovered], :not([data-rac]):hover) {
259
+ --_tint: var(--medo-color-state-hover-on-media);
260
+ }
261
+
262
+ .medo-button--media:is([data-pressed], :not([data-rac]):active),
263
+ .medo-button--media-ghost:is([data-pressed], :not([data-rac]):active) {
264
+ --_tint: var(--medo-color-state-pressed-on-media);
265
+ }
266
+
267
+ .medo-button--icon-only:is(.medo-button--media, .medo-button--media-ghost).medo-button--sm {
268
+ inline-size: calc(var(--medo-space-6) + var(--medo-space-1)); /* 28 */
269
+ block-size: calc(var(--medo-space-6) + var(--medo-space-1));
270
+ }
271
+
272
+ .medo-button--icon-only:is(.medo-button--media, .medo-button--media-ghost).medo-button--sm::before {
273
+ content: '';
274
+ position: absolute;
275
+ inset: calc(-1 * var(--medo-space-2)); /* 44 hit area */
276
+ border-radius: var(--medo-radius-full);
277
+ }
278
+
279
+ /* ---------- Button group ----------
280
+ * The main action sits at the end of the reading path.
281
+ * footer – right, primary rightmost, destructive left (forms, dialogs, card footers)
282
+ * header – right, next to a title, size sm
283
+ * content – left, primary first (hero, teaser, empty state)
284
+ * Mobile < 640 px: footer and content stack full width with the primary on top.
285
+ * DOM order = desktop reading order; mobile reorders visually only.
286
+ */
287
+
288
+ .medo-button-group {
289
+ display: flex;
290
+ flex-wrap: wrap;
291
+ align-items: center;
292
+ gap: var(--medo-space-2);
293
+ }
294
+
295
+ .medo-button-group--footer,
296
+ .medo-button-group--header {
297
+ justify-content: flex-end;
298
+ }
299
+
300
+ .medo-button-group__destructive {
301
+ margin-inline-end: auto;
302
+ }
303
+
304
+ @media (max-width: 639.98px) {
305
+ /* breakpoint/sm (640) */
306
+ .medo-button-group--footer,
307
+ .medo-button-group--content {
308
+ flex-direction: column;
309
+ align-items: stretch;
310
+ }
311
+ .medo-button-group--footer {
312
+ flex-direction: column-reverse;
313
+ }
314
+ .medo-button-group--footer .medo-button-group__destructive {
315
+ order: -1; /* last on screen in column-reverse */
316
+ margin-inline-end: 0;
317
+ display: grid; /* the button fills the width like the others */
318
+ }
319
+ }