@junoput01/junoui 0.2.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 (136) hide show
  1. package/CHANGELOG.md +199 -0
  2. package/README.md +117 -0
  3. package/dist/android/colors.xml +94 -0
  4. package/dist/android/dimens.xml +49 -0
  5. package/dist/css/juno-fonts.css +45 -0
  6. package/dist/css/juno-tokens.css +345 -0
  7. package/dist/css/juno.css +4481 -0
  8. package/dist/flutter/juno_tokens.dart +143 -0
  9. package/dist/fonts/b612-400.woff2 +0 -0
  10. package/dist/fonts/b612-700.woff2 +0 -0
  11. package/dist/fonts/b612-mono-400.woff2 +0 -0
  12. package/dist/fonts/b612-mono-700.woff2 +0 -0
  13. package/dist/icons/juno-icons.svg +69 -0
  14. package/dist/ios/JunoTokens.swift +149 -0
  15. package/dist/js/tokens.d.ts +11 -0
  16. package/dist/js/tokens.js +244 -0
  17. package/dist/json/tokens.json +921 -0
  18. package/dist/scss/_juno-tokens.scss +176 -0
  19. package/package.json +92 -0
  20. package/src/css/base.css +120 -0
  21. package/src/css/components/accordion.css +83 -0
  22. package/src/css/components/alert.css +90 -0
  23. package/src/css/components/avatar.css +82 -0
  24. package/src/css/components/badge.css +55 -0
  25. package/src/css/components/breadcrumb.css +64 -0
  26. package/src/css/components/button.css +59 -0
  27. package/src/css/components/card.css +68 -0
  28. package/src/css/components/checkbox.css +89 -0
  29. package/src/css/components/chip.css +89 -0
  30. package/src/css/components/divider.css +50 -0
  31. package/src/css/components/dock.css +74 -0
  32. package/src/css/components/dot.css +35 -0
  33. package/src/css/components/drawer.css +80 -0
  34. package/src/css/components/field.css +37 -0
  35. package/src/css/components/gauge.css +78 -0
  36. package/src/css/components/icon.css +35 -0
  37. package/src/css/components/input.css +60 -0
  38. package/src/css/components/list.css +135 -0
  39. package/src/css/components/loader.css +159 -0
  40. package/src/css/components/menu.css +114 -0
  41. package/src/css/components/modal.css +161 -0
  42. package/src/css/components/navbar.css +103 -0
  43. package/src/css/components/pagination.css +72 -0
  44. package/src/css/components/pillbar.css +111 -0
  45. package/src/css/components/popover.css +73 -0
  46. package/src/css/components/rail.css +96 -0
  47. package/src/css/components/readout.css +45 -0
  48. package/src/css/components/segmented.css +99 -0
  49. package/src/css/components/select.css +38 -0
  50. package/src/css/components/skeleton.css +62 -0
  51. package/src/css/components/slider.css +117 -0
  52. package/src/css/components/spark.css +26 -0
  53. package/src/css/components/stepper.css +121 -0
  54. package/src/css/components/switch.css +131 -0
  55. package/src/css/components/table.css +443 -0
  56. package/src/css/components/tabs.css +66 -0
  57. package/src/css/components/thumb.css +69 -0
  58. package/src/css/components/toast.css +109 -0
  59. package/src/css/components/toggle-button.css +91 -0
  60. package/src/css/components/tooltip.css +155 -0
  61. package/src/css/density.css +40 -0
  62. package/src/css/fonts.css +45 -0
  63. package/src/css/layout.css +99 -0
  64. package/src/css/typescale.css +27 -0
  65. package/src/css/utilities.css +76 -0
  66. package/src/fonts/LICENSE +93 -0
  67. package/src/fonts/b612-400.woff2 +0 -0
  68. package/src/fonts/b612-700.woff2 +0 -0
  69. package/src/fonts/b612-mono-400.woff2 +0 -0
  70. package/src/fonts/b612-mono-700.woff2 +0 -0
  71. package/src/icons/LICENSE +21 -0
  72. package/src/icons/arrow-clockwise.svg +1 -0
  73. package/src/icons/arrow-down.svg +1 -0
  74. package/src/icons/arrow-left.svg +1 -0
  75. package/src/icons/arrow-right.svg +1 -0
  76. package/src/icons/arrow-up.svg +1 -0
  77. package/src/icons/arrows-clockwise.svg +1 -0
  78. package/src/icons/arrows-out.svg +1 -0
  79. package/src/icons/bell.svg +1 -0
  80. package/src/icons/calendar-blank.svg +1 -0
  81. package/src/icons/caret-down.svg +1 -0
  82. package/src/icons/caret-left.svg +1 -0
  83. package/src/icons/caret-right.svg +1 -0
  84. package/src/icons/caret-up.svg +1 -0
  85. package/src/icons/chat-circle.svg +1 -0
  86. package/src/icons/check-circle.svg +1 -0
  87. package/src/icons/check.svg +1 -0
  88. package/src/icons/clock.svg +1 -0
  89. package/src/icons/cloud-arrow-down.svg +1 -0
  90. package/src/icons/cloud.svg +1 -0
  91. package/src/icons/copy.svg +1 -0
  92. package/src/icons/cpu.svg +1 -0
  93. package/src/icons/dots-three-vertical.svg +1 -0
  94. package/src/icons/dots-three.svg +1 -0
  95. package/src/icons/download.svg +1 -0
  96. package/src/icons/envelope.svg +1 -0
  97. package/src/icons/eye-slash.svg +1 -0
  98. package/src/icons/eye.svg +1 -0
  99. package/src/icons/file.svg +1 -0
  100. package/src/icons/film-strip.svg +1 -0
  101. package/src/icons/floppy-disk.svg +1 -0
  102. package/src/icons/folder.svg +1 -0
  103. package/src/icons/funnel.svg +1 -0
  104. package/src/icons/gear.svg +1 -0
  105. package/src/icons/hard-drives.svg +1 -0
  106. package/src/icons/heart.svg +1 -0
  107. package/src/icons/hexagon.svg +1 -0
  108. package/src/icons/house.svg +1 -0
  109. package/src/icons/images.svg +1 -0
  110. package/src/icons/info.svg +1 -0
  111. package/src/icons/link.svg +1 -0
  112. package/src/icons/list.svg +1 -0
  113. package/src/icons/lock-open.svg +1 -0
  114. package/src/icons/lock.svg +1 -0
  115. package/src/icons/magnifying-glass.svg +1 -0
  116. package/src/icons/minus.svg +1 -0
  117. package/src/icons/moon.svg +1 -0
  118. package/src/icons/pencil-simple.svg +1 -0
  119. package/src/icons/play.svg +1 -0
  120. package/src/icons/plus.svg +1 -0
  121. package/src/icons/puzzle-piece.svg +1 -0
  122. package/src/icons/question.svg +1 -0
  123. package/src/icons/sliders-horizontal.svg +1 -0
  124. package/src/icons/sliders.svg +1 -0
  125. package/src/icons/squares-four.svg +1 -0
  126. package/src/icons/star.svg +1 -0
  127. package/src/icons/sun.svg +1 -0
  128. package/src/icons/trash.svg +1 -0
  129. package/src/icons/upload-simple.svg +1 -0
  130. package/src/icons/upload.svg +1 -0
  131. package/src/icons/user.svg +1 -0
  132. package/src/icons/users.svg +1 -0
  133. package/src/icons/warning-circle.svg +1 -0
  134. package/src/icons/warning.svg +1 -0
  135. package/src/icons/x-circle.svg +1 -0
  136. package/src/icons/x.svg +1 -0
@@ -0,0 +1,443 @@
1
+ /* ════════════════════════════════════════════════════════════════════
2
+ * Component — Table / Data grid
3
+ * Dense, scannable rows that read like instrument readouts: numbers in
4
+ * mono, right-aligned; semantic color reserved for status; one selected
5
+ * row carries the active rail. junoui ships the LOOK + a11y contract;
6
+ * the app owns sort, selection, inline-edit, pagination, and filtering.
7
+ *
8
+ * Real <table> semantics — junoui only dresses it. Wrap in
9
+ * `.juno-table-scroll` for a sticky-header, overflow-scrolling viewport.
10
+ * Usage:
11
+ * <div class="juno-table-scroll">
12
+ * <table class="juno-table juno-table--zebra juno-table--sticky">
13
+ * <thead><tr>
14
+ * <th aria-sort="ascending">FLIGHT</th>
15
+ * <th class="juno-table__num" aria-sort="none">ALT</th>
16
+ * </tr></thead>
17
+ * <tbody>
18
+ * <tr aria-selected="true"><td>…</td><td class="juno-table__num">37,000</td></tr>
19
+ * </tbody>
20
+ * </table>
21
+ * </div>
22
+ * ════════════════════════════════════════════════════════════════════ */
23
+
24
+ /* ── scroll viewport (sticky header lives here) ───────────────────────── */
25
+ .juno-table-scroll {
26
+ max-block-size: 480px;
27
+ overflow: auto;
28
+ scrollbar-width: thin;
29
+ scrollbar-color: var(--juno-s3) transparent;
30
+ }
31
+
32
+ .juno-table-scroll::-webkit-scrollbar { inline-size: 10px; block-size: 10px; }
33
+ .juno-table-scroll::-webkit-scrollbar-thumb { background: var(--juno-s3); border-radius: var(--juno-radius-5); }
34
+ .juno-table-scroll::-webkit-scrollbar-track { background: transparent; }
35
+
36
+ /* ── table ────────────────────────────────────────────────────────────── */
37
+ .juno-table {
38
+ inline-size: 100%;
39
+ border-collapse: separate;
40
+ border-spacing: 0;
41
+ font-family: var(--juno-font-family-sans);
42
+ font-size: var(--juno-font-size-13);
43
+ color: var(--juno-data);
44
+ }
45
+
46
+ /* header */
47
+ .juno-table thead th {
48
+ padding: var(--juno-space-12) var(--juno-space-16);
49
+ background: var(--juno-s2);
50
+ font-size: var(--juno-font-size-10);
51
+ font-weight: var(--juno-font-weight-medium);
52
+ letter-spacing: var(--juno-font-tracking-caps);
53
+ text-transform: uppercase;
54
+ color: var(--juno-label);
55
+ text-align: start;
56
+ white-space: nowrap;
57
+ vertical-align: middle;
58
+ user-select: none;
59
+ border-block-end: var(--juno-border-width-1) solid var(--juno-control-edge);
60
+ }
61
+
62
+ .juno-table--sticky thead th {
63
+ position: sticky;
64
+ inset-block-start: 0;
65
+ z-index: var(--juno-z-raised);
66
+ }
67
+
68
+ /* sortable header — set aria-sort; the arrow + active color follow */
69
+ .juno-table thead th[aria-sort] { cursor: pointer; }
70
+
71
+ .juno-table thead th[aria-sort]::after {
72
+ content: '';
73
+ display: inline-block;
74
+ inline-size: var(--juno-space-8);
75
+ margin-inline-start: var(--juno-space-4);
76
+ font-family: var(--juno-font-family-mono);
77
+ font-size: 9px;
78
+ color: var(--juno-active);
79
+ }
80
+
81
+ .juno-table thead th[aria-sort='none']:hover { color: var(--juno-data); }
82
+ .juno-table thead th[aria-sort='none']:hover::after { content: '↕'; color: var(--juno-muted); }
83
+ .juno-table thead th[aria-sort='ascending'] { color: var(--juno-data); }
84
+ .juno-table thead th[aria-sort='ascending']::after { content: '▲'; }
85
+ .juno-table thead th[aria-sort='descending'] { color: var(--juno-data); }
86
+ .juno-table thead th[aria-sort='descending']::after { content: '▼'; }
87
+
88
+ /* body cells */
89
+ .juno-table tbody td {
90
+ padding: var(--juno-space-12) var(--juno-space-16);
91
+ color: var(--juno-data);
92
+ white-space: nowrap;
93
+ vertical-align: middle;
94
+ border-block-end: var(--juno-border-width-1) solid var(--juno-border);
95
+ }
96
+
97
+ /* density — per-table, independent of the global density mode */
98
+ .juno-table--compact thead th { padding: var(--juno-space-8) var(--juno-space-12); }
99
+ .juno-table--compact tbody td { padding: var(--juno-space-8) var(--juno-space-16); }
100
+
101
+ /* zebra (set on the table; striping the odd body rows) */
102
+ .juno-table--zebra tbody tr:nth-child(even) td { background: var(--juno-s2); }
103
+ :root[data-juno-mode='light'] .juno-table--zebra tbody tr:nth-child(even) td { background: var(--juno-s1); }
104
+
105
+ /* row interaction — app keeps aria-selected in sync */
106
+ .juno-table tbody tr { transition: background var(--juno-motion-duration-quick) var(--juno-motion-ease-standard); }
107
+ .juno-table tbody tr:hover td { background: var(--juno-s2); }
108
+ :root[data-juno-mode='light'] .juno-table tbody tr:hover td { background: var(--juno-s1); }
109
+
110
+ .juno-table tbody tr[aria-selected='true'] td {
111
+ background: color-mix(in srgb, var(--juno-active) 11%, transparent);
112
+ }
113
+
114
+ /* selection rail — a left bar on the first cell of a selected row */
115
+ .juno-table tbody td:first-child {
116
+ border-inline-start: var(--juno-border-width-3) solid transparent;
117
+ }
118
+
119
+ .juno-table tbody tr[aria-selected='true'] td:first-child {
120
+ border-inline-start-color: var(--juno-active);
121
+ }
122
+
123
+ /* ── cell flavors ─────────────────────────────────────────────────────── */
124
+
125
+ /* identifier — mono, bold (callsigns, codes, IDs) */
126
+ .juno-table__id {
127
+ font-family: var(--juno-font-family-mono);
128
+ font-weight: var(--juno-font-weight-bold);
129
+ }
130
+
131
+ /* numeric — mono, bold, end-aligned (measured values) */
132
+ .juno-table__num {
133
+ font-family: var(--juno-font-family-mono);
134
+ font-weight: var(--juno-font-weight-bold);
135
+ text-align: end;
136
+ }
137
+
138
+ /* secondary mono (routes, sub-codes) — quieter than data */
139
+ .juno-table__mono {
140
+ font-family: var(--juno-font-family-mono);
141
+ color: var(--juno-label);
142
+ }
143
+
144
+ /* timestamp — mono, muted */
145
+ .juno-table__time {
146
+ font-family: var(--juno-font-family-mono);
147
+ text-align: end;
148
+ color: var(--juno-label);
149
+ }
150
+
151
+ /* trend / delta — role-colored mono; author supplies ▲ / ▼ + sign */
152
+ .juno-table__trend {
153
+ --juno-role: var(--juno-nominal);
154
+
155
+ display: inline-flex;
156
+ align-items: center;
157
+ gap: var(--juno-space-4);
158
+ font-family: var(--juno-font-family-mono);
159
+ font-weight: var(--juno-font-weight-bold);
160
+ color: var(--juno-role);
161
+ }
162
+
163
+ /* inline meter — track + role fill + value; app sets --juno-table-fill (0–100) */
164
+ .juno-table__meter {
165
+ --juno-role: var(--juno-nominal);
166
+
167
+ display: inline-flex;
168
+ align-items: center;
169
+ gap: var(--juno-space-8);
170
+ justify-content: flex-end;
171
+ inline-size: 100%;
172
+ }
173
+
174
+ .juno-table__meter-track {
175
+ inline-size: var(--juno-space-56);
176
+ block-size: var(--juno-space-4);
177
+ border-radius: var(--juno-radius-3);
178
+ background: var(--juno-control-surface);
179
+ border: var(--juno-border-width-1) solid var(--juno-border);
180
+ overflow: hidden;
181
+ flex-shrink: 0;
182
+ }
183
+
184
+ .juno-table__meter-fill {
185
+ display: block;
186
+ block-size: 100%;
187
+ inline-size: calc(var(--juno-table-fill, 0) * 1%);
188
+ background: var(--juno-role);
189
+ border-radius: var(--juno-radius-3);
190
+ }
191
+
192
+ .juno-table__meter-val {
193
+ font-family: var(--juno-font-family-mono);
194
+ font-size: var(--juno-font-size-12);
195
+ color: var(--juno-role);
196
+ min-inline-size: var(--juno-space-32);
197
+ text-align: end;
198
+ }
199
+
200
+ /* row actions — icon buttons, revealed when the row is hovered/focused */
201
+ .juno-table__actions {
202
+ display: inline-flex;
203
+ gap: var(--juno-space-8);
204
+ opacity: 0;
205
+ transition: opacity var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
206
+ }
207
+
208
+ .juno-table tbody tr:hover .juno-table__actions,
209
+ .juno-table tbody tr:focus-within .juno-table__actions { opacity: 1; }
210
+
211
+ /* no hover to reveal them on touch — actions stay visible */
212
+ @media (hover: none) {
213
+ .juno-table__actions { opacity: 1; }
214
+ }
215
+
216
+ .juno-table__action {
217
+ display: inline-flex;
218
+ align-items: center;
219
+ justify-content: center;
220
+ inline-size: var(--juno-space-24);
221
+ block-size: var(--juno-space-24);
222
+ border-radius: var(--juno-radius-3);
223
+ border: var(--juno-border-width-1) solid var(--juno-control-edge);
224
+ background: transparent;
225
+ color: var(--juno-label);
226
+ font-size: var(--juno-font-size-13);
227
+ cursor: pointer;
228
+ }
229
+
230
+ .juno-table__action:hover { color: var(--juno-data); background: var(--juno-s2); }
231
+
232
+ /* ── overflow behaviors (set on a cell) ───────────────────────────────────
233
+ Scoped under `.juno-table td` so they out-specify the base `white-space:
234
+ nowrap` on body cells — otherwise wrap / clamp would never break. */
235
+ .juno-table td.juno-table__truncate {
236
+ max-inline-size: var(--juno-cell-max, 240px);
237
+ overflow: hidden;
238
+ text-overflow: ellipsis;
239
+ white-space: nowrap;
240
+ }
241
+
242
+ /* Line-clamp must live on a block child: a <td> stays `table-cell` and ignores
243
+ `display: -webkit-box`, so clamping it leaks the overflowing line. The td just
244
+ sets the width + wrap; the inner element does the clamp. */
245
+ .juno-table td.juno-table__clamp {
246
+ max-inline-size: var(--juno-cell-max, 240px);
247
+ white-space: normal;
248
+ }
249
+
250
+ .juno-table__clamp-text {
251
+ display: -webkit-box;
252
+ -webkit-line-clamp: 2;
253
+ line-clamp: 2;
254
+ -webkit-box-orient: vertical;
255
+ overflow: hidden;
256
+ }
257
+
258
+ .juno-table td.juno-table__wrap {
259
+ max-inline-size: var(--juno-cell-max, 240px);
260
+ white-space: normal;
261
+ overflow-wrap: anywhere;
262
+ }
263
+
264
+ /* ── selection column (checkbox) ──────────────────────────────────────── */
265
+ .juno-table th.juno-table__check,
266
+ .juno-table td.juno-table__check {
267
+ inline-size: 44px;
268
+ text-align: center;
269
+ padding-inline: var(--juno-space-16) var(--juno-space-8);
270
+ }
271
+
272
+ /* ── editable cells (app swaps the mark for an input on edit) ──────────── */
273
+ .juno-table td.juno-table__editable { cursor: cell; }
274
+
275
+ .juno-table td.juno-table__editable:hover {
276
+ background: color-mix(in srgb, var(--juno-active) 8%, transparent);
277
+ box-shadow: inset 0 0 0 var(--juno-border-width-1) var(--juno-active);
278
+ }
279
+
280
+ /* the dashed underline that marks a value as editable */
281
+ .juno-table__mark {
282
+ border-block-end: var(--juno-border-width-1) dashed color-mix(in srgb, var(--juno-label) 50%, transparent);
283
+ padding-block-end: 1px;
284
+ }
285
+
286
+ /* the inline editor — a compact input that fills the cell */
287
+ .juno-table__edit-input {
288
+ inline-size: 100%;
289
+ background: var(--juno-control-surface);
290
+ border: var(--juno-border-width-1) solid var(--juno-active);
291
+ border-radius: var(--juno-radius-3);
292
+ padding: var(--juno-space-2) var(--juno-space-8);
293
+ font-family: var(--juno-font-family-mono);
294
+ font-size: var(--juno-font-size-13);
295
+ color: var(--juno-data);
296
+ text-align: inherit;
297
+ outline: none;
298
+ }
299
+
300
+ /* ── toolbar / footer / bulk bar (framing rows) ───────────────────────── */
301
+ .juno-table__toolbar,
302
+ .juno-table__foot {
303
+ display: flex;
304
+ align-items: center;
305
+ gap: var(--juno-space-12);
306
+ flex-wrap: wrap;
307
+ padding: var(--juno-space-12) var(--juno-space-16);
308
+ background: var(--juno-s1);
309
+ }
310
+
311
+ .juno-table__toolbar { border-block-end: var(--juno-border-width-1) solid var(--juno-border); }
312
+ .juno-table__foot { border-block-start: var(--juno-border-width-1) solid var(--juno-border); }
313
+
314
+ /* selection / bulk-action bar — shown by the app while rows are selected.
315
+ The [hidden] override is required: a class `display` beats the UA [hidden]. */
316
+ .juno-table__bulk {
317
+ display: flex;
318
+ align-items: center;
319
+ gap: var(--juno-space-16);
320
+ padding: var(--juno-space-10) var(--juno-space-16);
321
+ background: color-mix(in srgb, var(--juno-active) 10%, transparent);
322
+ border-block-end: var(--juno-border-width-1) solid var(--juno-active);
323
+ }
324
+
325
+ .juno-table__bulk[hidden] { display: none; }
326
+
327
+ .juno-table__bulk-count {
328
+ font-family: var(--juno-font-family-mono);
329
+ font-weight: var(--juno-font-weight-bold);
330
+ font-size: var(--juno-font-size-12);
331
+ color: var(--juno-active);
332
+ }
333
+
334
+ /* ── skeleton + empty states ──────────────────────────────────────────── */
335
+ .juno-table__skeleton {
336
+ block-size: var(--juno-space-10);
337
+ border-radius: var(--juno-radius-3);
338
+ background: linear-gradient(
339
+ 90deg,
340
+ var(--juno-s2) 0%,
341
+ var(--juno-s3) 50%,
342
+ var(--juno-s2) 100%
343
+ );
344
+ background-size: 200% 100%;
345
+ animation: juno-table-shimmer 1.2s ease-in-out infinite;
346
+ }
347
+
348
+ @keyframes juno-table-shimmer {
349
+ from { background-position: 200% 0; }
350
+ to { background-position: -200% 0; }
351
+ }
352
+
353
+ .juno-table__empty {
354
+ display: flex;
355
+ flex-direction: column;
356
+ align-items: center;
357
+ justify-content: center;
358
+ gap: var(--juno-space-12);
359
+ padding: var(--juno-space-56) var(--juno-space-24);
360
+ text-align: center;
361
+ color: var(--juno-label);
362
+ }
363
+
364
+ .juno-table__empty-icon {
365
+ display: flex;
366
+ align-items: center;
367
+ justify-content: center;
368
+ inline-size: var(--juno-space-40);
369
+ block-size: var(--juno-space-40);
370
+ border-radius: var(--juno-radius-8);
371
+ border: var(--juno-border-width-1) solid var(--juno-control-edge);
372
+ color: var(--juno-muted);
373
+ font-family: var(--juno-font-family-mono);
374
+ font-size: var(--juno-font-size-18);
375
+ }
376
+
377
+ @media (prefers-reduced-motion: reduce) {
378
+ .juno-table__skeleton { animation: none; }
379
+ }
380
+
381
+ /* ── stacked mode (opt-in) — card rows for phone-width containers ────────
382
+ Add `.juno-table--stack` on the table and `data-label="HEADER"` on each
383
+ td. In a container narrower than 480px the grid flips: the header row
384
+ hides, every row becomes a bordered card, every cell a label/value pair
385
+ (::before renders the data-label). Container-driven, so it needs an
386
+ ancestor with `container-type: inline-size` — usually the
387
+ .juno-table-scroll wrapper. Semantics stay a real <table>; only the
388
+ display changes.
389
+ Usage:
390
+ <div class="juno-table-scroll" style="container-type:inline-size">
391
+ <table class="juno-table juno-table--stack">
392
+ <tbody><tr>
393
+ <td data-label="FLIGHT" class="juno-table__id">JU-204</td>
394
+ <td data-label="ALT" class="juno-table__num">37,000</td>
395
+ </tr></tbody> */
396
+ @container (max-width: 480px) {
397
+ .juno-table--stack thead { display: none; }
398
+
399
+ .juno-table--stack,
400
+ .juno-table--stack tbody,
401
+ .juno-table--stack tbody tr { display: block; }
402
+
403
+ .juno-table--stack tbody tr {
404
+ margin-block-end: var(--juno-space-12);
405
+ border: var(--juno-border-width-1) solid var(--juno-border);
406
+ border-radius: var(--juno-radius-5);
407
+ overflow: hidden;
408
+ }
409
+
410
+ .juno-table--stack tbody td {
411
+ display: flex;
412
+ align-items: center;
413
+ justify-content: space-between;
414
+ gap: var(--juno-space-16);
415
+ padding: var(--juno-space-8) var(--juno-space-16);
416
+ border-block-end: none;
417
+ white-space: normal;
418
+ text-align: end;
419
+ }
420
+
421
+ /* the column header, rebuilt per cell from data-label */
422
+ .juno-table--stack tbody td::before {
423
+ content: attr(data-label);
424
+ flex-shrink: 0;
425
+ font-family: var(--juno-font-family-sans);
426
+ font-size: var(--juno-font-size-10);
427
+ font-weight: var(--juno-font-weight-medium);
428
+ letter-spacing: var(--juno-font-tracking-caps);
429
+ text-transform: uppercase;
430
+ text-align: start;
431
+ color: var(--juno-label);
432
+ }
433
+
434
+ /* the selection rail moves from the first cell to the card edge */
435
+ .juno-table--stack tbody td:first-child { border-inline-start: none; }
436
+
437
+ .juno-table--stack tbody tr[aria-selected='true'] {
438
+ border-inline-start: var(--juno-border-width-3) solid var(--juno-active);
439
+ }
440
+
441
+ /* no hover on most narrow devices — keep row actions visible */
442
+ .juno-table--stack .juno-table__actions { opacity: 1; }
443
+ }
@@ -0,0 +1,66 @@
1
+ /* ════════════════════════════════════════════════════════════════════
2
+ * Component — Tabs
3
+ * A horizontal tablist with an active-cyan underline indicator; the
4
+ * selected tab shows its panel. junoui ships the look + the ARIA
5
+ * contract; the app owns switching (aria-selected, panel visibility,
6
+ * arrow-key roving focus).
7
+ * Usage:
8
+ * <div class="juno-tabs">
9
+ * <div class="juno-tabs__list" role="tablist" aria-label="Service">
10
+ * <button class="juno-tabs__tab" role="tab" id="t1"
11
+ * aria-controls="p1" aria-selected="true">OVERVIEW</button>
12
+ * <button class="juno-tabs__tab" role="tab" id="t2"
13
+ * aria-controls="p2" aria-selected="false">METRICS</button>
14
+ * </div>
15
+ * <div class="juno-tabs__panel" role="tabpanel" id="p1" aria-labelledby="t1">…</div>
16
+ * <div class="juno-tabs__panel" role="tabpanel" id="p2" aria-labelledby="t2" hidden>…</div>
17
+ * </div>
18
+ * ════════════════════════════════════════════════════════════════════ */
19
+
20
+ .juno-tabs__list {
21
+ display: flex;
22
+ gap: var(--juno-space-2);
23
+ border-block-end: var(--juno-border-width-1) solid var(--juno-border);
24
+
25
+ /* tabs never wrap — when space runs out (phone widths) the strip
26
+ scrolls sideways instead, keeping every tab reachable */
27
+ overflow: auto hidden;
28
+ overscroll-behavior-inline: contain;
29
+ scrollbar-width: none;
30
+ }
31
+
32
+ .juno-tabs__tab {
33
+ appearance: none;
34
+ margin: 0;
35
+ margin-block-end: calc(-1 * var(--juno-border-width-1));
36
+ padding: var(--juno-space-10) var(--juno-space-16);
37
+ background: transparent;
38
+ border: none;
39
+ border-block-end: var(--juno-border-width-2) solid transparent;
40
+ font-family: var(--juno-font-family-sans);
41
+ font-size: var(--juno-font-size-13);
42
+ font-weight: var(--juno-font-weight-medium);
43
+ letter-spacing: var(--juno-font-tracking-label);
44
+ color: var(--juno-label);
45
+ white-space: nowrap;
46
+ cursor: pointer;
47
+ transition: color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
48
+ }
49
+
50
+ .juno-tabs__tab:hover { color: var(--juno-data); }
51
+
52
+ .juno-tabs__tab[aria-selected='true'] {
53
+ color: var(--juno-active);
54
+ border-block-end-color: var(--juno-active);
55
+ }
56
+
57
+ .juno-tabs__tab:disabled {
58
+ opacity: var(--juno-opacity-disabled);
59
+ cursor: not-allowed;
60
+ }
61
+
62
+ .juno-tabs__panel {
63
+ padding-block-start: var(--juno-space-20);
64
+ }
65
+
66
+ .juno-tabs__panel[hidden] { display: none; }
@@ -0,0 +1,69 @@
1
+ /* ════════════════════════════════════════════════════════════════════
2
+ * Component — Thumb (media thumbnail + unavailable placeholder)
3
+ * A media frame that never shows the browser's broken-image glyph:
4
+ * a muted placeholder (icon on s2) sits under the media; the image
5
+ * covers it while present. Loading state is .juno-skeleton's job —
6
+ * this is for media that is missing or failed.
7
+ * A missing thumb is not a warning: the placeholder stays neutral.
8
+ * Usage:
9
+ * <figure class="juno-thumb" style="aspect-ratio: 1">
10
+ * <img src="…" alt="Sunset clip" onerror="this.remove()" />
11
+ * </figure>
12
+ * <!-- known-missing: ship no <img>; label the figure instead -->
13
+ * <figure class="juno-thumb juno-thumb--video" role="img" aria-label="Preview unavailable"></figure>
14
+ * The onerror one-liner is the whole JS contract (optional, stateless):
15
+ * a broken image removes itself and the placeholder underneath shows.
16
+ * ════════════════════════════════════════════════════════════════════ */
17
+
18
+ .juno-thumb {
19
+ position: relative;
20
+ display: flex;
21
+ flex-direction: column;
22
+ align-items: center;
23
+ justify-content: center;
24
+ gap: var(--juno-space-4);
25
+ margin: 0;
26
+ overflow: hidden;
27
+ background: var(--juno-s2);
28
+ border: var(--juno-border-width-1) solid var(--juno-border);
29
+ border-radius: var(--juno-radius-3);
30
+ }
31
+
32
+ /* placeholder glyph (Phosphor "images", masked so it takes any color) */
33
+ .juno-thumb::before {
34
+ --juno-thumb-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M160,88a16,16,0,1,1,16,16A16,16,0,0,1,160,88Zm76-32V160a20,20,0,0,1-20,20H204v20a20,20,0,0,1-20,20H40a20,20,0,0,1-20-20V88A20,20,0,0,1,40,68H60V56A20,20,0,0,1,80,36H216A20,20,0,0,1,236,56ZM180,180H80a20,20,0,0,1-20-20V92H44V196H180Zm-21.66-24L124,121.66,89.66,156ZM212,60H84v67.72l25.86-25.86a20,20,0,0,1,28.28,0L192.28,156H212Z'/%3E%3C/svg%3E");
35
+
36
+ content: '';
37
+ inline-size: 28%;
38
+ max-inline-size: var(--juno-space-32);
39
+ aspect-ratio: 1;
40
+ background: var(--juno-muted);
41
+ -webkit-mask: var(--juno-thumb-glyph) center / contain no-repeat;
42
+ mask: var(--juno-thumb-glyph) center / contain no-repeat;
43
+ }
44
+
45
+ /* video flavor — play glyph */
46
+ .juno-thumb--video::before {
47
+ --juno-thumb-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M234.49,111.07,90.41,22.94A20,20,0,0,0,60,39.87V216.13a20,20,0,0,0,30.41,16.93l144.08-88.13a19.82,19.82,0,0,0,0-33.86ZM84,208.85V47.15L216.16,128Z'/%3E%3C/svg%3E");
48
+ }
49
+
50
+ /* optional text under the glyph (keeps color from being the only signal) */
51
+ .juno-thumb__label {
52
+ font-family: var(--juno-font-family-sans);
53
+ font-size: var(--juno-font-size-10);
54
+ font-weight: var(--juno-font-weight-bold);
55
+ letter-spacing: var(--juno-font-tracking-caps);
56
+ text-transform: uppercase;
57
+ color: var(--juno-muted);
58
+ }
59
+
60
+ /* the media covers the frame; while present the placeholder sits hidden
61
+ underneath. On error the app's onerror removes the element. */
62
+ .juno-thumb > img,
63
+ .juno-thumb > video {
64
+ position: absolute;
65
+ inset: 0;
66
+ inline-size: 100%;
67
+ block-size: 100%;
68
+ object-fit: cover;
69
+ }
@@ -0,0 +1,109 @@
1
+ /* ════════════════════════════════════════════════════════════════════
2
+ * Component — Toast / snackbar
3
+ * A transient, floating notification. Same role-rail language as the
4
+ * alert, but elevated (shadow) and stacked in a fixed corner on the
5
+ * reserved alert layer (--juno-z-alert, above modals). junoui ships the
6
+ * look + enter/exit motion; the app owns creation, auto-dismiss timing,
7
+ * and live-region announcement.
8
+ * Usage:
9
+ * <div class="juno-toast-stack" id="toasts" aria-live="polite"></div>
10
+ * // app appends:
11
+ * <div class="juno-toast juno--nominal" role="status">
12
+ * <span class="juno-toast__icon" aria-hidden="true">✓</span>
13
+ * <span class="juno-toast__text">Build promoted.</span>
14
+ * <button class="juno-toast__close" aria-label="Dismiss">✕</button>
15
+ * </div>
16
+ * ════════════════════════════════════════════════════════════════════ */
17
+
18
+ .juno-toast-stack {
19
+ position: fixed;
20
+ z-index: var(--juno-z-alert);
21
+ inset-block-end: var(--juno-space-24);
22
+ inset-inline-end: var(--juno-space-24);
23
+ display: flex;
24
+ flex-direction: column;
25
+ gap: var(--juno-space-12);
26
+ inline-size: min(360px, calc(100vw - var(--juno-space-32)));
27
+ pointer-events: none;
28
+ }
29
+
30
+ /* placements */
31
+ .juno-toast-stack--top { inset-block: var(--juno-space-24) auto; }
32
+ .juno-toast-stack--start { inset-inline: var(--juno-space-24) auto; }
33
+
34
+ .juno-toast {
35
+ --juno-role: var(--juno-active);
36
+
37
+ pointer-events: auto;
38
+ display: flex;
39
+ align-items: center;
40
+ gap: var(--juno-space-12);
41
+ padding: var(--juno-space-12) var(--juno-space-16);
42
+ background: var(--juno-s2);
43
+ border: var(--juno-border-width-1) solid var(--juno-border);
44
+ border-inline-start: var(--juno-border-width-3) solid var(--juno-role);
45
+ border-radius: var(--juno-radius-5);
46
+ box-shadow: var(--juno-shadow-2);
47
+ color: var(--juno-data);
48
+ transition:
49
+ opacity var(--juno-motion-duration-quick) var(--juno-motion-ease-decel),
50
+ transform var(--juno-motion-duration-quick) var(--juno-motion-ease-decel);
51
+ }
52
+
53
+ /* enter (freshly inserted) + leaving (app adds the class before removal) */
54
+ @starting-style {
55
+ .juno-toast { opacity: 0; transform: translateX(var(--juno-space-16)); }
56
+ }
57
+
58
+ .juno-toast--leaving { opacity: 0; transform: translateX(var(--juno-space-16)); }
59
+
60
+ .juno-toast__icon {
61
+ flex-shrink: 0;
62
+ color: var(--juno-role);
63
+ font-size: var(--juno-font-size-16);
64
+ line-height: var(--juno-font-lineHeight-none);
65
+ }
66
+
67
+ .juno-toast__text {
68
+ flex: 1;
69
+ min-inline-size: 0;
70
+ font-size: var(--juno-font-size-13);
71
+ color: var(--juno-data);
72
+ }
73
+
74
+ .juno-toast__close {
75
+ flex-shrink: 0;
76
+ appearance: none;
77
+ padding: 0;
78
+ background: transparent;
79
+ border: none;
80
+ color: var(--juno-muted);
81
+ font-size: var(--juno-font-size-16);
82
+ line-height: var(--juno-font-lineHeight-none);
83
+ cursor: pointer;
84
+ }
85
+
86
+ .juno-toast__close:hover { color: var(--juno-data); }
87
+
88
+ /* ── narrow viewports — snackbar style ──────────────────────────────────
89
+ The corner stack goes full-width at the bottom edge (thumb reach),
90
+ clear of the home indicator; toasts slide up instead of sideways. */
91
+ @media (width <= 639.98px) {
92
+ .juno-toast-stack {
93
+ inset-inline: var(--juno-space-12);
94
+ inset-block-end: calc(var(--juno-space-12) + env(safe-area-inset-bottom, 0));
95
+ inline-size: auto;
96
+ }
97
+
98
+ .juno-toast-stack--top { inset-block: var(--juno-space-12) auto; }
99
+
100
+ @starting-style {
101
+ .juno-toast { transform: translateY(var(--juno-space-16)); }
102
+ }
103
+
104
+ .juno-toast--leaving { transform: translateY(var(--juno-space-16)); }
105
+ }
106
+
107
+ @media (prefers-reduced-motion: reduce) {
108
+ .juno-toast { transition: none; }
109
+ }