@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
+ * Alert – med.o Design System
3
+ *
4
+ * Persistent message inside the page. Plain markup:
5
+ * <div class="medo-alert medo-alert--info">
6
+ * <svg class="medo-alert__icon" aria-hidden="true">…</svg>
7
+ * <div class="medo-alert__content">
8
+ * <p class="medo-alert__title">Title</p>
9
+ * <div class="medo-alert__description">Description</div>
10
+ * <div class="medo-alert__actions">…text buttons…</div>
11
+ * </div>
12
+ * <button class="medo-alert__close">…</button>
13
+ * </div>
14
+ * Tones: info · success · warning · danger. Variants: inline (default) · banner (full width).
15
+ */
16
+
17
+ .medo-alert {
18
+ --_bg: var(--medo-color-feedback-info-surface);
19
+ --_border: var(--medo-color-feedback-info-border);
20
+ --_icon: var(--medo-color-feedback-info-text);
21
+
22
+ box-sizing: border-box;
23
+ display: flex;
24
+ align-items: flex-start;
25
+ gap: var(--medo-space-3);
26
+ padding: var(--medo-space-4);
27
+ padding-inline-end: var(--medo-space-3);
28
+ border: var(--medo-border-width-thin) solid var(--_border);
29
+ border-radius: var(--medo-radius-md);
30
+ background-color: var(--_bg);
31
+ color: var(--medo-color-text-primary);
32
+ }
33
+
34
+ .medo-alert--success {
35
+ --_bg: var(--medo-color-feedback-success-surface);
36
+ --_border: var(--medo-color-feedback-success-border);
37
+ --_icon: var(--medo-color-feedback-success-text);
38
+ }
39
+ .medo-alert--warning {
40
+ --_bg: var(--medo-color-feedback-warning-surface);
41
+ --_border: var(--medo-color-feedback-warning-border);
42
+ --_icon: var(--medo-color-feedback-warning-text);
43
+ }
44
+ .medo-alert--danger {
45
+ --_bg: var(--medo-color-feedback-danger-surface);
46
+ --_border: var(--medo-color-feedback-danger-border);
47
+ --_icon: var(--medo-color-feedback-danger-text);
48
+ }
49
+
50
+ .medo-alert__icon {
51
+ flex-shrink: 0;
52
+ inline-size: var(--medo-size-icon-md);
53
+ block-size: var(--medo-size-icon-md);
54
+ color: var(--_icon);
55
+ stroke-width: 2; /* 1.67 px at 20 px */
56
+ }
57
+
58
+ .medo-alert__content {
59
+ display: grid;
60
+ gap: var(--medo-space-1);
61
+ flex: 1;
62
+ min-inline-size: 0;
63
+ /* Centre the first line with the 20 px icon (label/md has a 20 px line). */
64
+ padding-block-start: 0;
65
+ }
66
+
67
+ .medo-alert__title {
68
+ margin: 0;
69
+ font: var(--medo-typography-label-md);
70
+ color: var(--medo-color-text-primary);
71
+ }
72
+
73
+ .medo-alert__description {
74
+ font: var(--medo-typography-body-sm);
75
+ color: var(--medo-color-text-secondary);
76
+ }
77
+
78
+ .medo-alert__description > :first-child {
79
+ margin-block-start: 0;
80
+ }
81
+ .medo-alert__description > :last-child {
82
+ margin-block-end: 0;
83
+ }
84
+
85
+ .medo-alert__actions {
86
+ display: flex;
87
+ flex-wrap: wrap;
88
+ gap: var(--medo-space-4);
89
+ margin-block-start: var(--medo-space-2);
90
+ }
91
+
92
+ /* The close button (Icon Button ghost sm) sits in the top-right corner. */
93
+ .medo-alert__close {
94
+ flex-shrink: 0;
95
+ margin-block: calc(-1 * var(--medo-space-1-5));
96
+ }
97
+
98
+ /* ---------- Banner: full width, bottom border only, one line ---------- */
99
+
100
+ .medo-alert--banner {
101
+ align-items: center;
102
+ padding: var(--medo-space-2) var(--medo-space-6);
103
+ border-width: 0 0 var(--medo-border-width-thin);
104
+ border-radius: 0;
105
+ }
106
+
107
+ .medo-alert--banner .medo-alert__content {
108
+ display: flex;
109
+ flex-wrap: wrap;
110
+ align-items: baseline;
111
+ column-gap: var(--medo-space-3);
112
+ }
113
+
114
+ .medo-alert--banner .medo-alert__actions {
115
+ margin-block-start: 0;
116
+ }
117
+
118
+ .medo-alert--banner .medo-alert__close {
119
+ margin-block: 0;
120
+ }
121
+
122
+ @media (forced-colors: active) {
123
+ .medo-alert {
124
+ border-color: CanvasText;
125
+ }
126
+ }
127
+
128
+ /* An alert that appears (after an action) fades in and settles 4 px with a smooth spring. */
129
+ .medo-alert {
130
+ transition:
131
+ opacity var(--medo-motion-duration-normal) var(--medo-motion-easing-enter),
132
+ translate var(--medo-motion-spring-smooth-duration) var(--medo-motion-spring-smooth);
133
+ }
134
+ @starting-style {
135
+ .medo-alert {
136
+ opacity: 0;
137
+ translate: 0 calc(-1 * var(--medo-space-1));
138
+ }
139
+ }
140
+
141
+ @media (prefers-reduced-motion: reduce) {
142
+ @starting-style {
143
+ .medo-alert {
144
+ translate: none;
145
+ }
146
+ }
147
+ }
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Avatar – med.o Design System
3
+ *
4
+ * Fallback chain: image → initials → icon. Circle = person, square = organization.
5
+ * <span class="medo-avatar medo-avatar--md medo-avatar--circle medo-avatar--teal" role="img" aria-label="Lea M.">
6
+ * <span class="medo-avatar__initials" aria-hidden="true">LM</span>
7
+ * <img class="medo-avatar__image" src="…" alt="">
8
+ * </span>
9
+ *
10
+ * Sizes xs 20 · sm 24 · md 32 · lg 40 · xl 56 · 2xl 80
11
+ * Colours: blue · violet · pink · amber · green · teal (from the user id) · neutral (icon)
12
+ */
13
+
14
+ .medo-avatar {
15
+ --_size: var(--medo-space-8);
16
+ --_bg: var(--medo-color-surface-subtle);
17
+ --_fg: var(--medo-color-text-secondary);
18
+ --_radius: var(--medo-radius-md);
19
+ --_status: calc(var(--medo-space-2) + var(--medo-space-0-5)); /* 10 */
20
+
21
+ position: relative;
22
+ box-sizing: border-box;
23
+ display: inline-grid;
24
+ place-items: center;
25
+ flex-shrink: 0;
26
+ inline-size: var(--_size);
27
+ block-size: var(--_size);
28
+ border-radius: var(--medo-radius-full);
29
+ background-color: var(--_bg);
30
+ color: var(--_fg);
31
+ vertical-align: middle;
32
+ user-select: none;
33
+ }
34
+
35
+ /* Initials, image and icon share one grid cell: the image covers the initials once loaded. */
36
+ .medo-avatar > * {
37
+ grid-area: 1 / 1;
38
+ }
39
+
40
+ .medo-avatar--square {
41
+ border-radius: var(--_radius);
42
+ }
43
+
44
+ /* Sizes. Initials use the avatar text styles. */
45
+ .medo-avatar--xs {
46
+ --_size: var(--medo-space-5);
47
+ --_radius: var(--medo-radius-sm);
48
+ --_status: var(--medo-space-2);
49
+ font: var(--medo-typography-avatar-xs);
50
+ }
51
+ .medo-avatar--sm {
52
+ --_size: var(--medo-space-6);
53
+ --_radius: var(--medo-radius-sm);
54
+ --_status: var(--medo-space-2);
55
+ font: var(--medo-typography-avatar-sm);
56
+ }
57
+ .medo-avatar--md {
58
+ font: var(--medo-typography-avatar-md);
59
+ }
60
+ .medo-avatar--lg {
61
+ --_size: var(--medo-space-10);
62
+ font: var(--medo-typography-avatar-lg);
63
+ }
64
+ .medo-avatar--xl {
65
+ --_size: calc(var(--medo-space-12) + var(--medo-space-2)); /* 56 */
66
+ --_radius: var(--medo-radius-lg);
67
+ --_status: calc(var(--medo-space-3) + var(--medo-space-0-5)); /* 14 */
68
+ font: var(--medo-typography-avatar-xl);
69
+ }
70
+ .medo-avatar--2xl {
71
+ --_size: var(--medo-space-20);
72
+ --_radius: var(--medo-radius-lg);
73
+ --_status: calc(var(--medo-space-3) + var(--medo-space-0-5));
74
+ font: var(--medo-typography-avatar-2xl);
75
+ }
76
+
77
+ /* Tones: same person, same colour (picked from the id). */
78
+ .medo-avatar--blue {
79
+ --_bg: var(--medo-color-avatar-blue-surface);
80
+ --_fg: var(--medo-color-avatar-blue-text);
81
+ }
82
+ .medo-avatar--violet {
83
+ --_bg: var(--medo-color-avatar-violet-surface);
84
+ --_fg: var(--medo-color-avatar-violet-text);
85
+ }
86
+ .medo-avatar--pink {
87
+ --_bg: var(--medo-color-avatar-pink-surface);
88
+ --_fg: var(--medo-color-avatar-pink-text);
89
+ }
90
+ .medo-avatar--amber {
91
+ --_bg: var(--medo-color-avatar-amber-surface);
92
+ --_fg: var(--medo-color-avatar-amber-text);
93
+ }
94
+ .medo-avatar--green {
95
+ --_bg: var(--medo-color-avatar-green-surface);
96
+ --_fg: var(--medo-color-avatar-green-text);
97
+ }
98
+ .medo-avatar--teal {
99
+ --_bg: var(--medo-color-avatar-teal-surface);
100
+ --_fg: var(--medo-color-avatar-teal-text);
101
+ }
102
+
103
+ .medo-avatar__initials {
104
+ text-transform: uppercase;
105
+ line-height: 1;
106
+ }
107
+
108
+ .medo-avatar__icon {
109
+ display: inline-flex;
110
+ }
111
+
112
+ /* Icon: 12 / 14 / 16 / 20 / 28 / 40 px = 60 % at xs, 58 % at sm, 50 % from md. */
113
+ .medo-avatar__icon > svg {
114
+ inline-size: calc(var(--_size) * 0.5);
115
+ block-size: calc(var(--_size) * 0.5);
116
+ }
117
+ .medo-avatar--xs .medo-avatar__icon > svg {
118
+ inline-size: calc(var(--_size) * 0.6);
119
+ block-size: calc(var(--_size) * 0.6);
120
+ }
121
+ .medo-avatar--sm .medo-avatar__icon > svg {
122
+ inline-size: calc(var(--_size) * 7 / 12);
123
+ block-size: calc(var(--_size) * 7 / 12);
124
+ }
125
+
126
+ .medo-avatar__image {
127
+ inline-size: 100%;
128
+ block-size: 100%;
129
+ object-fit: cover;
130
+ border-radius: inherit;
131
+ }
132
+
133
+ /* 1 px inner border so light photos don't blend into the page. */
134
+ .medo-avatar--has-image::after {
135
+ content: '';
136
+ grid-area: 1 / 1;
137
+ inline-size: 100%;
138
+ block-size: 100%;
139
+ border-radius: inherit;
140
+ box-shadow: inset 0 0 0 1px var(--medo-color-avatar-image-border);
141
+ pointer-events: none;
142
+ }
143
+
144
+ /* ---------- Status (bottom right, 2 px ring in the surface colour) ---------- */
145
+
146
+ .medo-avatar__status {
147
+ position: absolute;
148
+ inset-block-end: 0;
149
+ inset-inline-end: 0;
150
+ box-sizing: border-box;
151
+ inline-size: var(--_status);
152
+ block-size: var(--_status);
153
+ border-radius: var(--medo-radius-full);
154
+ box-shadow: 0 0 0 2px var(--medo-color-surface-default);
155
+ }
156
+
157
+ .medo-avatar__status--online {
158
+ background-color: var(--medo-color-feedback-success-solid);
159
+ }
160
+ .medo-avatar__status--away {
161
+ background-color: var(--medo-color-feedback-warning-solid);
162
+ }
163
+ .medo-avatar__status--busy {
164
+ background-color: var(--medo-color-feedback-danger-solid);
165
+ }
166
+ .medo-avatar__status--offline {
167
+ background-color: var(--medo-color-border-strong);
168
+ }
169
+
170
+ /* ---------- Group: max 4 + “+N”, 25 % overlap, 2 px ring ---------- */
171
+
172
+ .medo-avatar-group {
173
+ display: inline-flex;
174
+ align-items: center;
175
+ isolation: isolate;
176
+ }
177
+
178
+ .medo-avatar-group > .medo-avatar {
179
+ box-shadow: 0 0 0 2px var(--medo-color-surface-default);
180
+ }
181
+
182
+ .medo-avatar-group > .medo-avatar + .medo-avatar {
183
+ margin-inline-start: calc(var(--_size) * -0.25);
184
+ }
185
+
186
+ .medo-avatar--more {
187
+ font: var(--medo-typography-label-sm);
188
+ padding: 0;
189
+ border: 0;
190
+ }
191
+
192
+ .medo-avatar-group--xs .medo-avatar--more,
193
+ .medo-avatar-group--sm .medo-avatar--more {
194
+ font: var(--medo-typography-label-xs);
195
+ }
196
+
197
+ button.medo-avatar--more {
198
+ cursor: pointer;
199
+ outline: none;
200
+ }
201
+ button.medo-avatar--more[data-hovered] {
202
+ background-image: linear-gradient(var(--medo-color-state-hover), var(--medo-color-state-hover));
203
+ }
204
+ button.medo-avatar--more[data-pressed] {
205
+ background-image: linear-gradient(var(--medo-color-state-pressed), var(--medo-color-state-pressed));
206
+ }
207
+ .medo-avatar-group > button.medo-avatar--more[data-focus-visible] {
208
+ box-shadow:
209
+ 0 0 0 2px var(--medo-color-surface-default),
210
+ var(--medo-shadow-focus-ring);
211
+ }
212
+
213
+ @media (forced-colors: active) {
214
+ .medo-avatar {
215
+ border: var(--medo-border-width-thin) solid CanvasText;
216
+ }
217
+ .medo-avatar__status {
218
+ forced-color-adjust: none;
219
+ background-color: CanvasText;
220
+ }
221
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Badge – med.o Design System
3
+ *
4
+ * Short, non-interactive status label. Plain CSS, works in every framework:
5
+ * <span class="medo-badge medo-badge--success medo-badge--md">
6
+ * <span class="medo-badge__dot" aria-hidden="true"></span>Active
7
+ * </span>
8
+ *
9
+ * Count (notifications): <span class="medo-badge-count medo-badge-count--danger medo-badge-count--md">3</span>
10
+ * Dot (unread, presence): <span class="medo-badge-dot medo-badge-dot--success"></span>
11
+ *
12
+ * Tones: neutral · brand · success · warning · danger · info
13
+ */
14
+
15
+ .medo-badge {
16
+ --_bg: var(--medo-color-surface-subtle);
17
+ --_fg: var(--medo-color-text-secondary);
18
+
19
+ box-sizing: border-box;
20
+ display: inline-flex;
21
+ align-items: center;
22
+ gap: var(--medo-space-1);
23
+ block-size: var(--medo-space-6);
24
+ padding-inline: var(--medo-space-2);
25
+ border-radius: var(--medo-radius-sm);
26
+ background-color: var(--_bg);
27
+ color: var(--_fg);
28
+ font: var(--medo-typography-label-sm);
29
+ white-space: nowrap;
30
+ vertical-align: middle;
31
+ }
32
+
33
+ .medo-badge--sm {
34
+ block-size: var(--medo-space-5);
35
+ padding-inline: var(--medo-space-1-5);
36
+ }
37
+
38
+ .medo-badge--brand {
39
+ --_bg: var(--medo-color-surface-brand-subtle);
40
+ --_fg: var(--medo-color-text-brand);
41
+ }
42
+ .medo-badge--success {
43
+ --_bg: var(--medo-color-feedback-success-surface);
44
+ --_fg: var(--medo-color-feedback-success-text);
45
+ }
46
+ .medo-badge--warning {
47
+ --_bg: var(--medo-color-feedback-warning-surface);
48
+ --_fg: var(--medo-color-feedback-warning-text);
49
+ }
50
+ .medo-badge--danger {
51
+ --_bg: var(--medo-color-feedback-danger-surface);
52
+ --_fg: var(--medo-color-feedback-danger-text);
53
+ }
54
+ .medo-badge--info {
55
+ --_bg: var(--medo-color-feedback-info-surface);
56
+ --_fg: var(--medo-color-feedback-info-text);
57
+ }
58
+
59
+ /* Leading dot (6 px) or icon (12 px) in the text colour – status not by colour alone. */
60
+ .medo-badge__dot {
61
+ flex-shrink: 0;
62
+ inline-size: var(--medo-space-1-5);
63
+ block-size: var(--medo-space-1-5);
64
+ border-radius: var(--medo-radius-full);
65
+ background-color: currentColor;
66
+ }
67
+
68
+ .medo-badge__icon {
69
+ display: inline-flex;
70
+ flex-shrink: 0;
71
+ }
72
+
73
+ .medo-badge__icon > svg {
74
+ inline-size: var(--medo-space-3);
75
+ block-size: var(--medo-space-3);
76
+ stroke-width: 3; /* 1.5 px at 12 px */
77
+ }
78
+
79
+ /* ---------- Count ---------- */
80
+
81
+ .medo-badge-count {
82
+ --_bg: var(--medo-color-feedback-danger-solid);
83
+ --_fg: var(--medo-color-feedback-danger-on-solid);
84
+
85
+ box-sizing: border-box;
86
+ display: inline-flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ min-inline-size: var(--medo-space-5);
90
+ block-size: var(--medo-space-5);
91
+ padding-inline: var(--medo-space-1);
92
+ border-radius: var(--medo-radius-full);
93
+ background-color: var(--_bg);
94
+ color: var(--_fg);
95
+ font: var(--medo-typography-label-sm);
96
+ font-variant-numeric: tabular-nums;
97
+ white-space: nowrap;
98
+ }
99
+
100
+ .medo-badge-count--sm {
101
+ min-inline-size: var(--medo-space-4);
102
+ block-size: var(--medo-space-4);
103
+ font: var(--medo-typography-label-xs);
104
+ }
105
+
106
+ .medo-badge-count--brand {
107
+ --_bg: var(--medo-color-action-primary);
108
+ --_fg: var(--medo-color-text-on-action);
109
+ }
110
+ .medo-badge-count--neutral {
111
+ --_bg: var(--medo-color-surface-inverse);
112
+ --_fg: var(--medo-color-text-inverse);
113
+ }
114
+
115
+ /* ---------- Dot ---------- */
116
+
117
+ .medo-badge-dot {
118
+ display: inline-block;
119
+ flex-shrink: 0;
120
+ inline-size: var(--medo-space-2);
121
+ block-size: var(--medo-space-2);
122
+ border-radius: var(--medo-radius-full);
123
+ background-color: var(--medo-color-text-secondary);
124
+ }
125
+
126
+ .medo-badge-dot--brand {
127
+ background-color: var(--medo-color-action-primary);
128
+ }
129
+ .medo-badge-dot--success {
130
+ background-color: var(--medo-color-feedback-success-solid);
131
+ }
132
+ .medo-badge-dot--warning {
133
+ background-color: var(--medo-color-feedback-warning-solid);
134
+ }
135
+ .medo-badge-dot--danger {
136
+ background-color: var(--medo-color-feedback-danger-solid);
137
+ }
138
+ .medo-badge-dot--info {
139
+ background-color: var(--medo-color-feedback-info-solid);
140
+ }
141
+
142
+ /* High contrast mode of the operating system (forced-colors) removes backgrounds: keep the shapes visible. */
143
+ @media (forced-colors: active) {
144
+ .medo-badge,
145
+ .medo-badge-count {
146
+ border: var(--medo-border-width-thin) solid CanvasText;
147
+ }
148
+ .medo-badge__dot,
149
+ .medo-badge-dot {
150
+ forced-color-adjust: none;
151
+ background-color: CanvasText;
152
+ }
153
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Bottom Sheet – med.o Design System
3
+ *
4
+ * Modal layer from the bottom on small screens.
5
+ * <div class="medo-sheet-overlay"> scrim, overlay/scrim
6
+ * <div class="medo-sheet [medo-sheet--full]"> surface/raised, top radius/xl, elevation/4
7
+ * <section class="medo-sheet__dialog" role="dialog">
8
+ * <div class="medo-sheet__grab"> handle (decorative) + header: title and close button </div>
9
+ * <div class="medo-sheet__content">…scrolls…</div>
10
+ * <div class="medo-sheet__footer">…sticky, safe area…</div>
11
+ * </section>
12
+ * </div>
13
+ * </div>
14
+ * Height = content, max 90dvh (full: fixed 90dvh, e.g. with search). In: smooth spring; out: 250 ms ease-in; reduced motion: fade.
15
+ * State contract: [data-entering] [data-exiting] on overlay and sheet (React Aria).
16
+ */
17
+
18
+ .medo-sheet-overlay {
19
+ position: fixed;
20
+ inset: 0;
21
+ z-index: var(--medo-layer-modal);
22
+ display: flex;
23
+ align-items: flex-end;
24
+ background-color: var(--medo-color-overlay-scrim);
25
+ }
26
+
27
+ .medo-sheet-overlay {
28
+ transition: opacity var(--medo-motion-duration-normal) var(--medo-motion-easing-enter);
29
+ }
30
+ .medo-sheet-overlay[data-entering] {
31
+ opacity: 0;
32
+ }
33
+ .medo-sheet-overlay[data-exiting] {
34
+ opacity: 0;
35
+ transition: opacity var(--medo-motion-duration-normal) var(--medo-motion-easing-exit);
36
+ }
37
+
38
+ .medo-sheet {
39
+ box-sizing: border-box;
40
+ display: flex;
41
+ flex-direction: column;
42
+ inline-size: 100%;
43
+ max-block-size: 90dvh;
44
+ border-start-start-radius: var(--medo-radius-xl);
45
+ border-start-end-radius: var(--medo-radius-xl);
46
+ background-color: var(--medo-color-surface-raised);
47
+ box-shadow: var(--medo-shadow-elevation-4);
48
+ color: var(--medo-color-text-primary);
49
+ outline: none;
50
+ touch-action: pan-y;
51
+ /* Slides with a smooth spring – also back when a swipe is too short. */
52
+ transition: transform var(--medo-motion-spring-smooth-duration) var(--medo-motion-spring-smooth);
53
+ }
54
+
55
+ /* With search: fixed height, so the sheet does not jump while typing or when the keyboard opens. */
56
+ .medo-sheet--full {
57
+ block-size: 90dvh;
58
+ }
59
+
60
+ .medo-sheet[data-entering],
61
+ .medo-sheet-overlay[data-entering] .medo-sheet {
62
+ transform: translateY(100%);
63
+ }
64
+ .medo-sheet[data-exiting],
65
+ .medo-sheet-overlay[data-exiting] .medo-sheet {
66
+ transform: translateY(100%);
67
+ transition: transform var(--medo-motion-duration-normal) var(--medo-motion-easing-exit);
68
+ }
69
+
70
+ /*
71
+ * Select sheet (popover): React Aria positions popovers with inline styles next to the trigger.
72
+ * The sheet covers the viewport instead – !important is the only way to beat inline styles.
73
+ */
74
+ .medo-sheet-overlay--popover {
75
+ position: fixed !important;
76
+ inset: 0 !important;
77
+ inline-size: auto !important;
78
+ block-size: auto !important;
79
+ max-block-size: none !important;
80
+ max-inline-size: none !important;
81
+ }
82
+
83
+ /* Select sheet (popover): the scrim fills the popover, the sheet sits at the bottom. */
84
+ .medo-sheet-scrim {
85
+ display: flex;
86
+ flex: 1;
87
+ align-items: flex-end;
88
+ block-size: 100%;
89
+ }
90
+
91
+ /* While dragging: follow the finger, no transition. */
92
+ .medo-sheet[data-dragging] {
93
+ transition: none;
94
+ }
95
+
96
+ .medo-sheet__dialog {
97
+ display: flex;
98
+ flex: 1;
99
+ flex-direction: column;
100
+ min-block-size: 0;
101
+ outline: none;
102
+ }
103
+
104
+ /* Handle + header: the area that can be swiped down. */
105
+ .medo-sheet__grab {
106
+ flex: none;
107
+ touch-action: none;
108
+ cursor: grab;
109
+ }
110
+
111
+ .medo-sheet__handle {
112
+ display: block;
113
+ inline-size: var(--medo-space-8);
114
+ block-size: var(--medo-space-1);
115
+ margin: var(--medo-space-2) auto var(--medo-space-1);
116
+ border-radius: var(--medo-radius-full);
117
+ background-color: var(--medo-color-border-default);
118
+ }
119
+
120
+ .medo-sheet__header {
121
+ display: flex;
122
+ align-items: center;
123
+ justify-content: space-between;
124
+ gap: var(--medo-space-2);
125
+ padding-inline: var(--medo-space-4) var(--medo-space-2);
126
+ }
127
+
128
+ .medo-sheet__title {
129
+ flex: 1;
130
+ min-inline-size: 0;
131
+ margin: 0;
132
+ font: var(--medo-typography-heading-sm);
133
+ color: var(--medo-color-text-primary);
134
+ }
135
+
136
+ .medo-sheet__content {
137
+ flex: 1;
138
+ min-block-size: 0;
139
+ overflow: auto;
140
+ overscroll-behavior: contain;
141
+ }
142
+
143
+ /* Last block without footer: keep clear of the iPhone home indicator. */
144
+ .medo-sheet__content:last-child {
145
+ padding-block-end: env(safe-area-inset-bottom);
146
+ }
147
+
148
+ .medo-sheet__footer {
149
+ display: grid;
150
+ gap: var(--medo-space-2);
151
+ padding: var(--medo-space-4);
152
+ padding-block-end: calc(var(--medo-space-4) + env(safe-area-inset-bottom));
153
+ border-block-start: var(--medo-border-width-thin) solid var(--medo-color-border-default);
154
+ }
155
+
156
+ .medo-sheet__footer .medo-button {
157
+ inline-size: 100%;
158
+ }
159
+
160
+ .medo-sheet__count {
161
+ margin: 0;
162
+ font: var(--medo-typography-caption);
163
+ color: var(--medo-color-text-secondary);
164
+ }
165
+
166
+ /* With a back button the header starts closer to the edge. */
167
+ .medo-sheet__header:has(> .medo-button:first-child) {
168
+ padding-inline-start: var(--medo-space-1);
169
+ }
170
+
171
+ /* ---------- Select sheet ---------- */
172
+
173
+ .medo-sheet__search {
174
+ flex: none;
175
+ padding: var(--medo-space-2) var(--medo-space-4);
176
+ }
177
+
178
+ .medo-sheet .medo-listbox {
179
+ max-block-size: none;
180
+ padding-inline: var(--medo-space-1);
181
+ }
182
+
183
+ @media (forced-colors: active) {
184
+ .medo-sheet {
185
+ border: 1px solid CanvasText;
186
+ }
187
+ }
188
+
189
+ @media (prefers-reduced-motion: reduce) {
190
+ /* Reduced motion: the sheet fades with the scrim instead of sliding. */
191
+ .medo-sheet-overlay {
192
+ transition: opacity var(--medo-motion-duration-fast) var(--medo-motion-easing-standard);
193
+ }
194
+ .medo-sheet {
195
+ transition: opacity var(--medo-motion-duration-fast) var(--medo-motion-easing-standard);
196
+ }
197
+ .medo-sheet:is([data-entering], [data-exiting]),
198
+ .medo-sheet-overlay:is([data-entering], [data-exiting]) .medo-sheet {
199
+ transform: none;
200
+ opacity: 0;
201
+ }
202
+ }