@med-o/ui 0.0.0-stage → 0.1.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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -2
  3. package/dist/index.d.ts +2021 -0
  4. package/dist/index.js +4721 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +78 -4
  7. package/src/accordion/accordion.css +184 -0
  8. package/src/alert/alert.css +147 -0
  9. package/src/avatar/avatar.css +221 -0
  10. package/src/badge/badge.css +153 -0
  11. package/src/bottom-sheet/bottom-sheet.css +202 -0
  12. package/src/breadcrumbs/breadcrumbs.css +147 -0
  13. package/src/bulk-action-bar/bulk-action-bar.css +98 -0
  14. package/src/button/button.css +319 -0
  15. package/src/card/card.css +217 -0
  16. package/src/checkbox/choice.css +196 -0
  17. package/src/combobox/combobox.css +125 -0
  18. package/src/date-time/date-time.css +474 -0
  19. package/src/dialog/dialog.css +319 -0
  20. package/src/drawer/drawer.css +156 -0
  21. package/src/empty-state/empty-state.css +161 -0
  22. package/src/field/field.css +115 -0
  23. package/src/link/link.css +103 -0
  24. package/src/list/list.css +172 -0
  25. package/src/list-view/list-view.css +473 -0
  26. package/src/listbox/listbox.css +203 -0
  27. package/src/media/media.css +298 -0
  28. package/src/menu/menu.css +183 -0
  29. package/src/navigation/navigation.css +626 -0
  30. package/src/pagination/pagination.css +132 -0
  31. package/src/popover/popover.css +166 -0
  32. package/src/progress/progress.css +225 -0
  33. package/src/search/search.css +309 -0
  34. package/src/select/select.css +229 -0
  35. package/src/slider/slider.css +212 -0
  36. package/src/styles.css +40 -0
  37. package/src/switch/switch.css +160 -0
  38. package/src/table/table.css +276 -0
  39. package/src/tabs/tabs.css +211 -0
  40. package/src/tag/tag.css +225 -0
  41. package/src/text-field/input.css +206 -0
  42. package/src/toast/toast.css +184 -0
  43. package/src/toggle-button/toggle-button.css +143 -0
  44. package/src/tooltip/tooltip.css +66 -0
  45. package/src/upload/upload.css +380 -0
  46. package/src/utils/utilities.css +14 -0
@@ -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
+ /* Windows high contrast mode 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
+ /* iOS highlight: pressed shows at once and fades out on release. 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
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Card – med.o Design System
3
+ *
4
+ * Container for one topic. Media, header actions, body and footer are optional; the title is required.
5
+ * <article class="medo-card medo-card--outlined">
6
+ * <div class="medo-card__media">…</div>
7
+ * <div class="medo-card__main">
8
+ * <div class="medo-card__header">
9
+ * <div class="medo-card__heading"><h3 class="medo-card__title">…</h3><p class="medo-card__subtitle">…</p></div>
10
+ * <div class="medo-card__header-actions">…</div>
11
+ * </div>
12
+ * <div class="medo-card__body">…</div>
13
+ * <div class="medo-card__footer">…</div>
14
+ * </div>
15
+ * </article>
16
+ *
17
+ * Variants: outlined (default) · elevated · filled · brand · plain. Density: standard (24) · compact (16).
18
+ * Interactive: the title link is stretched over the card (one click target); buttons stay above it.
19
+ */
20
+
21
+ .medo-card {
22
+ --_pad: var(--medo-space-6);
23
+ --_bg: var(--medo-color-surface-default);
24
+ --_border: var(--medo-color-border-default);
25
+
26
+ position: relative;
27
+ box-sizing: border-box;
28
+ display: flex;
29
+ flex-direction: column;
30
+ border: var(--medo-border-width-thin) solid var(--_border);
31
+ border-radius: var(--medo-radius-lg);
32
+ background-color: var(--_bg);
33
+ color: var(--medo-color-text-primary);
34
+ overflow: hidden;
35
+ isolation: isolate;
36
+ }
37
+
38
+ .medo-card--compact {
39
+ --_pad: var(--medo-space-4);
40
+ }
41
+
42
+ .medo-card--elevated {
43
+ box-shadow: var(--medo-shadow-elevation-1);
44
+ }
45
+ .medo-card--filled {
46
+ --_bg: var(--medo-color-surface-subtle);
47
+ --_border: transparent;
48
+ }
49
+ .medo-card--brand {
50
+ --_bg: var(--medo-color-surface-brand-subtle);
51
+ --_border: var(--medo-color-border-brand);
52
+ }
53
+ .medo-card--plain {
54
+ --_bg: transparent;
55
+ --_border: transparent;
56
+ --_pad: 0;
57
+ overflow: visible;
58
+ }
59
+
60
+ /* ---------- Parts ---------- */
61
+
62
+ .medo-card__media {
63
+ aspect-ratio: 16 / 9;
64
+ overflow: hidden;
65
+ background-color: var(--medo-color-surface-subtle);
66
+ }
67
+
68
+ .medo-card__media > :is(img, video, picture, .medo-image) {
69
+ display: block;
70
+ inline-size: 100%;
71
+ block-size: 100%;
72
+ object-fit: cover;
73
+ }
74
+
75
+ .medo-card--plain .medo-card__media {
76
+ border-radius: var(--medo-radius-lg);
77
+ margin-block-end: var(--medo-space-4);
78
+ }
79
+
80
+ .medo-card__main {
81
+ display: flex;
82
+ flex-direction: column;
83
+ gap: var(--medo-space-4);
84
+ flex: 1;
85
+ padding: var(--_pad);
86
+ }
87
+
88
+ .medo-card__header {
89
+ display: flex;
90
+ align-items: flex-start;
91
+ justify-content: space-between;
92
+ gap: var(--medo-space-4);
93
+ }
94
+
95
+ .medo-card__heading {
96
+ display: grid;
97
+ gap: var(--medo-space-1);
98
+ min-inline-size: 0;
99
+ }
100
+
101
+ .medo-card__title {
102
+ margin: 0;
103
+ font: var(--medo-typography-heading-sm);
104
+ color: var(--medo-color-text-primary);
105
+ }
106
+
107
+ .medo-card__subtitle {
108
+ margin: 0;
109
+ font: var(--medo-typography-body-sm);
110
+ color: var(--medo-color-text-secondary);
111
+ }
112
+
113
+ .medo-card__body {
114
+ font: var(--medo-typography-body-sm);
115
+ color: var(--medo-color-text-secondary);
116
+ }
117
+
118
+ .medo-card__body > :first-child {
119
+ margin-block-start: 0;
120
+ }
121
+ .medo-card__body > :last-child {
122
+ margin-block-end: 0;
123
+ }
124
+
125
+ /* Footer at the bottom, so cards in a row align. */
126
+ .medo-card__footer {
127
+ margin-block-start: auto;
128
+ }
129
+
130
+ /* ---------- Horizontal (lists): media 180 px wide on the left ---------- */
131
+
132
+ .medo-card--horizontal {
133
+ flex-direction: row;
134
+ }
135
+
136
+ .medo-card--horizontal .medo-card__media {
137
+ flex: 0 0 11.25rem; /* 180 px */
138
+ aspect-ratio: auto;
139
+ }
140
+
141
+ /* ---------- Interactive: stretched title link ---------- */
142
+
143
+ .medo-card__link {
144
+ color: inherit;
145
+ text-decoration: none;
146
+ outline: none;
147
+ }
148
+
149
+ /* The link covers the whole card. */
150
+ .medo-card__link::after {
151
+ content: '';
152
+ position: absolute;
153
+ inset: 0;
154
+ z-index: 0;
155
+ }
156
+
157
+ /* Buttons and other controls stay clickable above the link. */
158
+ .medo-card :is(.medo-card__header-actions, .medo-card__footer, .medo-card__body) :is(a, button, input, select, textarea, [tabindex]) {
159
+ position: relative;
160
+ z-index: 1;
161
+ }
162
+
163
+ .medo-card--interactive {
164
+ cursor: pointer;
165
+ }
166
+
167
+ .medo-card--interactive::before {
168
+ content: '';
169
+ position: absolute;
170
+ inset: 0;
171
+ border-radius: inherit;
172
+ background-color: transparent;
173
+ pointer-events: none;
174
+ z-index: 0;
175
+ transition: background-color var(--medo-motion-duration-fast) var(--medo-motion-easing-standard);
176
+ }
177
+
178
+ .medo-card--interactive:has(.medo-card__link[data-hovered])::before {
179
+ background-color: var(--medo-color-state-hover);
180
+ }
181
+
182
+ .medo-card--interactive:has(.medo-card__link[data-pressed])::before {
183
+ background-color: var(--medo-color-state-pressed);
184
+ }
185
+
186
+ .medo-card--interactive:has(.medo-card__link[data-focus-visible]) {
187
+ box-shadow: var(--medo-shadow-focus-ring);
188
+ }
189
+
190
+ .medo-card--interactive.medo-card--elevated:has(.medo-card__link[data-focus-visible]) {
191
+ box-shadow: var(--medo-shadow-focus-ring), var(--medo-shadow-elevation-1);
192
+ }
193
+
194
+ @media (forced-colors: active) {
195
+ .medo-card {
196
+ border-color: CanvasText;
197
+ }
198
+ .medo-card--interactive:has(.medo-card__link[data-focus-visible]) {
199
+ outline: var(--medo-focus-ring-width) solid Highlight;
200
+ outline-offset: 2px;
201
+ }
202
+ }
203
+
204
+ /* Interactive cards give way a little while pressed (iOS) and spring back. */
205
+ .medo-card--interactive {
206
+ transition: scale var(--medo-motion-spring-snappy-duration) var(--medo-motion-spring-snappy);
207
+ }
208
+ .medo-card--interactive:has(.medo-card__link[data-pressed]) {
209
+ scale: 0.98;
210
+ transition-duration: var(--medo-motion-duration-fast);
211
+ }
212
+
213
+ @media (prefers-reduced-motion: reduce) {
214
+ .medo-card--interactive:has(.medo-card__link[data-pressed]) {
215
+ scale: none;
216
+ }
217
+ }