@junoput01/junoui 0.2.0 → 0.4.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 (84) hide show
  1. package/CHANGELOG.md +251 -0
  2. package/README.md +23 -19
  3. package/dist/css/juno-custom-media.css +32 -0
  4. package/dist/css/juno.css +1355 -40
  5. package/dist/icons/inline.js +22 -0
  6. package/docs/accessibility.md +188 -0
  7. package/docs/boot-shell.md +295 -0
  8. package/docs/components/README.md +71 -0
  9. package/docs/components/accordion.md +49 -0
  10. package/docs/components/alert.md +46 -0
  11. package/docs/components/avatar.md +45 -0
  12. package/docs/components/badge.md +35 -0
  13. package/docs/components/breadcrumb.md +31 -0
  14. package/docs/components/button.md +32 -0
  15. package/docs/components/card.md +41 -0
  16. package/docs/components/checkbox.md +41 -0
  17. package/docs/components/chip.md +44 -0
  18. package/docs/components/divider.md +32 -0
  19. package/docs/components/dock.md +215 -0
  20. package/docs/components/drawer.md +100 -0
  21. package/docs/components/field.md +44 -0
  22. package/docs/components/fold-slot.md +31 -0
  23. package/docs/components/gauge.md +48 -0
  24. package/docs/components/icon-loader.md +122 -0
  25. package/docs/components/icon.md +70 -0
  26. package/docs/components/input.md +45 -0
  27. package/docs/components/list.md +76 -0
  28. package/docs/components/load-state.md +131 -0
  29. package/docs/components/loader.md +88 -0
  30. package/docs/components/menu.md +54 -0
  31. package/docs/components/modal.md +52 -0
  32. package/docs/components/navbar.md +53 -0
  33. package/docs/components/pagination.md +42 -0
  34. package/docs/components/pillbar.md +211 -0
  35. package/docs/components/popover.md +43 -0
  36. package/docs/components/rail.md +54 -0
  37. package/docs/components/readout.md +39 -0
  38. package/docs/components/reload.md +41 -0
  39. package/docs/components/segmented.md +46 -0
  40. package/docs/components/select.md +33 -0
  41. package/docs/components/skeleton.md +45 -0
  42. package/docs/components/slider.md +48 -0
  43. package/docs/components/spark.md +51 -0
  44. package/docs/components/status.md +30 -0
  45. package/docs/components/stepper.md +46 -0
  46. package/docs/components/switch.md +38 -0
  47. package/docs/components/table.md +126 -0
  48. package/docs/components/tabs.md +61 -0
  49. package/docs/components/thumb.md +81 -0
  50. package/docs/components/toast.md +48 -0
  51. package/docs/components/toggle-button.md +40 -0
  52. package/docs/components/tooltip.md +63 -0
  53. package/docs/design-guidelines.md +173 -0
  54. package/docs/flutter.md +42 -0
  55. package/docs/getting-started.md +93 -0
  56. package/docs/integration.md +95 -0
  57. package/docs/ios-conformance.md +224 -0
  58. package/docs/layout.md +271 -0
  59. package/docs/native.md +52 -0
  60. package/docs/roadmap.md +106 -0
  61. package/docs/tokens-reference.md +223 -0
  62. package/docs/web.md +95 -0
  63. package/package.json +7 -3
  64. package/src/css/base.css +171 -1
  65. package/src/css/components/dock.css +335 -0
  66. package/src/css/components/drawer.css +40 -3
  67. package/src/css/components/fold-slot.css +44 -0
  68. package/src/css/components/icon-loader.css +67 -0
  69. package/src/css/components/icon.css +6 -4
  70. package/src/css/components/load-state.css +136 -0
  71. package/src/css/components/loader.css +6 -0
  72. package/src/css/components/menu.css +4 -0
  73. package/src/css/components/modal.css +24 -3
  74. package/src/css/components/navbar.css +5 -1
  75. package/src/css/components/pillbar.css +222 -7
  76. package/src/css/components/rail.css +8 -0
  77. package/src/css/components/reload.css +48 -0
  78. package/src/css/components/skeleton.css +41 -13
  79. package/src/css/components/tabs.css +5 -0
  80. package/src/css/components/thumb.css +63 -1
  81. package/src/css/components/toast.css +5 -1
  82. package/src/css/density.css +22 -0
  83. package/src/css/layout.css +95 -5
  84. package/src/css/utilities.css +4 -1
@@ -9,10 +9,22 @@
9
9
  * <figure class="juno-thumb" style="aspect-ratio: 1">
10
10
  * <img src="…" alt="Sunset clip" onerror="this.remove()" />
11
11
  * </figure>
12
- * <!-- known-missing: ship no <img>; label the figure instead -->
12
+ * \3c !-- known-missing: ship no <img>; label the figure instead -->
13
13
  * <figure class="juno-thumb juno-thumb--video" role="img" aria-label="Preview unavailable"></figure>
14
14
  * The onerror one-liner is the whole JS contract (optional, stateless):
15
15
  * a broken image removes itself and the placeholder underneath shows.
16
+ * Aspect is locked by default (--juno-thumb-ratio, square) so a media wall's
17
+ * scroll height is stable before anything loads; override per instance with
18
+ * style="--juno-thumb-ratio: 16/9" (an inline aspect-ratio still wins too).
19
+ * Selection is an outline (never a border — a border would shift the frame)
20
+ * toggled by a class/data-attr; junoui never decides what's selected.
21
+ * Corner slots anchor badges/duration chips over the media with logical
22
+ * inset so they swap sides correctly under dir="rtl".
23
+ * Usage:
24
+ * <figure class="juno-thumb juno-thumb--selected">
25
+ * <img src="…" alt="Sunset clip" onerror="this.remove()" />
26
+ * <span class="juno-thumb__corner juno-thumb__corner--top-end badge">0:42</span>
27
+ * </figure>
16
28
  * ════════════════════════════════════════════════════════════════════ */
17
29
 
18
30
  .juno-thumb {
@@ -23,12 +35,30 @@
23
35
  justify-content: center;
24
36
  gap: var(--juno-space-4);
25
37
  margin: 0;
38
+ aspect-ratio: var(--juno-thumb-ratio, 1);
26
39
  overflow: hidden;
27
40
  background: var(--juno-s2);
28
41
  border: var(--juno-border-width-1) solid var(--juno-border);
29
42
  border-radius: var(--juno-radius-3);
30
43
  }
31
44
 
45
+ /* Flush — drop the border/radius for tight walls where the frame's own edge
46
+ would fight the grid (full-bleed tiles); the media stays edge-to-edge via
47
+ object-fit: cover regardless. */
48
+ .juno-thumb--flush {
49
+ border: none;
50
+ border-radius: 0;
51
+ }
52
+
53
+ /* Selected — an inset outline, not a border: it overlays the existing frame
54
+ edge instead of adding to the box, so nothing reflows when selection
55
+ toggles. Role-neutral (--juno-active), matching the checkbox/switch focus
56
+ ring elsewhere in the system. */
57
+ .juno-thumb--selected {
58
+ outline: var(--juno-border-width-2) solid var(--juno-active);
59
+ outline-offset: calc(-1 * var(--juno-border-width-2));
60
+ }
61
+
32
62
  /* placeholder glyph (Phosphor "images", masked so it takes any color) */
33
63
  .juno-thumb::before {
34
64
  --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");
@@ -67,3 +97,35 @@
67
97
  block-size: 100%;
68
98
  object-fit: cover;
69
99
  }
100
+
101
+ /* Corner overlay — anchor a badge, check, or duration chip over the media
102
+ without joining flow or touching the frame's aspect-ratio. Pick one
103
+ position modifier; the base only sets stacking + inset from the frame
104
+ edge. Logical inset (not top/left) so start/end swap under dir="rtl" —
105
+ and the modifiers are named start/end for the same reason: a `--top-right`
106
+ that renders top-LEFT in RTL would be a trap. */
107
+ .juno-thumb__corner {
108
+ position: absolute;
109
+ z-index: 1;
110
+ margin: var(--juno-space-4);
111
+ }
112
+
113
+ .juno-thumb__corner--top-start {
114
+ inset-block-start: 0;
115
+ inset-inline-start: 0;
116
+ }
117
+
118
+ .juno-thumb__corner--top-end {
119
+ inset-block-start: 0;
120
+ inset-inline-end: 0;
121
+ }
122
+
123
+ .juno-thumb__corner--bottom-start {
124
+ inset-block-end: 0;
125
+ inset-inline-start: 0;
126
+ }
127
+
128
+ .juno-thumb__corner--bottom-end {
129
+ inset-block-end: 0;
130
+ inset-inline-end: 0;
131
+ }
@@ -91,7 +91,11 @@
91
91
  @media (width <= 639.98px) {
92
92
  .juno-toast-stack {
93
93
  inset-inline: var(--juno-space-12);
94
- inset-block-end: calc(var(--juno-space-12) + env(safe-area-inset-bottom, 0));
94
+
95
+ /* unit-bearing env() fallback: inside calc() a unitless `0` is a <number>,
96
+ which invalidates the sum and drops the whole declaration (the stack
97
+ would then sit flush at 0 on any device without a safe area). */
98
+ inset-block-end: calc(var(--juno-space-12) + env(safe-area-inset-bottom, 0px));
95
99
  inline-size: auto;
96
100
  }
97
101
 
@@ -38,3 +38,25 @@
38
38
  --juno-tile-min: 108px;
39
39
  --juno-gap-content: var(--juno-space-4);
40
40
  }
41
+
42
+ /* auto — OPT-IN. `comfortable`/`compact` never change meaning; a consumer
43
+ * must explicitly write `data-juno-density="auto"` to get this behavior.
44
+ * Baseline (any pointer, any viewport ≥ 640px) is comfortable — no rule
45
+ * needed here, it inherits the :root defaults above by not matching
46
+ * `[data-juno-density='compact']`. Only a small-viewport COARSE pointer
47
+ * (phone, not a touch laptop) re-densifies, mirroring the precedent already
48
+ * set by base.css's own `@media (pointer: coarse)` tap-target rule — this is
49
+ * a viewport-level document concern (set on <html>/<body>), not a component
50
+ * in an unknown layout, so a viewport query is the right tool here (container
51
+ * queries can't see pointer type anyway).
52
+ *
53
+ * NEVER shrinks a control's min-height — --juno-size-tap-min stays governed
54
+ * solely by base.css's coarse-pointer rule (20260802-020 / WCAG 2.5.5).
55
+ * This block only shrinks padding/gap/tile sizing, same contract as compact. */
56
+ @media (pointer: coarse) and (width <= 639.98px) {
57
+ [data-juno-density='auto'] {
58
+ --juno-tile-min: 104px;
59
+ --juno-gap-content: var(--juno-space-2);
60
+ --juno-pad-surface-inline: var(--juno-space-12);
61
+ }
62
+ }
@@ -82,18 +82,108 @@
82
82
  flex-basis: calc((var(--juno-switcher-threshold, var(--juno-bp-sm)) - 100%) * 999);
83
83
  }
84
84
 
85
- /* Reelhorizontal scroll row (overflowing chips, timelines). */
85
+ /* Scrollerbare scroll-container primitive underneath every scrolling
86
+ region in the library (.juno-reel, .juno-app-shell__main, tab strips…).
87
+ Three knobs, each a custom prop with the sane-default fallback: overflow
88
+ axis, overscroll containment, and snap type — so a consumer opts into
89
+ containment/snap without re-deriving them, and opts OUT (snap: none is
90
+ the default here) without fighting a hardcoded value. `--x`/`--y` pick a
91
+ single scroll axis; `--bare` hides the scrollbar cross-browser; a
92
+ `.juno-snap` child opts into the snap stop once the container snaps.
93
+ <div class="juno-scroller juno-scroller--y">…</div>
94
+ <div class="juno-scroller juno-scroller--x juno-scroller--bare"
95
+ style="--juno-scroller-snap:x proximity">
96
+ <div class="juno-snap">…</div>
97
+ </div> */
98
+ .juno-scroller {
99
+ overflow: var(--juno-scroller-overflow, auto);
100
+ overscroll-behavior: var(--juno-scroller-overscroll, contain);
101
+ scroll-snap-type: var(--juno-scroller-snap, none);
102
+ }
103
+ .juno-scroller--x { overflow: auto hidden; }
104
+ .juno-scroller--y { overflow: hidden auto; }
105
+ .juno-scroller--bare { scrollbar-width: none; }
106
+ .juno-scroller--bare::-webkit-scrollbar { display: none; }
107
+ .juno-scroller > .juno-snap { scroll-snap-align: var(--juno-snap-align, start); }
108
+
109
+ /* Reel — horizontal scroll row (overflowing chips, timelines). A
110
+ .juno-scroller preset: single inline axis, mandatory snap by default —
111
+ override the mode per instance with --juno-scroller-snap (e.g. `inline
112
+ proximity` for a softer magnet, or `none` to opt out and keep only the
113
+ containment + layout). Default output is unchanged from before. */
86
114
  .juno-reel {
87
115
  display: flex;
88
116
  gap: var(--juno-reel-space, var(--juno-space-12));
89
117
  overflow-x: auto;
90
118
  overscroll-behavior-inline: contain;
91
- scroll-snap-type: inline mandatory;
119
+ scroll-snap-type: var(--juno-scroller-snap, inline mandatory);
92
120
  }
93
121
  .juno-reel > * { scroll-snap-align: start; flex: 0 0 auto; }
94
122
 
123
+ /* App shell — the standard product frame, as classes instead of the
124
+ copy-pasted \3c style> block every consumer used to hand-roll. A rail beside
125
+ a body column; the body holds an optional topbar, a scrolling main region,
126
+ and a dock/pillbar pinned at its foot. The MAIN region is the scroller
127
+ (not the page), so the dock never needs sticky/fixed to stay put and the
128
+ mobile address bar can't clip it. Height is 100dvh (dynamic viewport) so
129
+ it fills the visible area under shrinking browser chrome. Pairs with the
130
+ viewport helpers below to swap rail↔dock across md; see docs/layout.md#app-shell.
131
+ <div class="juno-app-shell">
132
+ <nav class="juno-rail juno-rail--responsive" aria-label="Primary">…</nav>
133
+ <div class="juno-app-shell__body">
134
+ <header class="juno-app-shell__topbar">…</header>
135
+ <main class="juno-app-shell__main">…</main>
136
+ <nav class="juno-dock juno-hide-from-md" aria-label="Primary">…</nav>
137
+ </div>
138
+ </div> */
139
+ .juno-app-shell {
140
+ display: flex;
141
+ block-size: 100dvh;
142
+ overflow: hidden; /* the main region scrolls, not the shell */
143
+ padding-inline: env(safe-area-inset-left, 0) env(safe-area-inset-right, 0);
144
+ }
145
+
146
+ .juno-app-shell__body {
147
+ flex: 1;
148
+ min-inline-size: 0;
149
+ display: flex;
150
+ flex-direction: column;
151
+ min-block-size: 0; /* let __main shrink and scroll instead of the shell */
152
+ }
153
+
154
+ .juno-app-shell__topbar {
155
+ flex-shrink: 0;
156
+ display: flex;
157
+ align-items: center;
158
+ gap: var(--juno-space-12);
159
+ min-block-size: var(--juno-app-shell-topbar-size, 46px);
160
+ padding-inline: var(--juno-pad-surface-inline);
161
+ padding-block-start: env(safe-area-inset-top, 0);
162
+ background: var(--juno-s1);
163
+ border-block-end: var(--juno-border-width-1) solid var(--juno-border);
164
+ }
165
+
166
+ .juno-app-shell__main {
167
+ flex: 1;
168
+ min-block-size: 0;
169
+ overflow: auto;
170
+ overscroll-behavior: contain;
171
+ padding: var(--juno-pad-surface-block) var(--juno-pad-surface-inline);
172
+ }
173
+
95
174
  /* Hide/show by viewport breakpoint — the rare case an intrinsic primitive
96
- can't express (e.g. swap a nav for a menu button). */
97
- @media (width <= 767.98px) { .juno-hide-below-md { display: none !important; } }
175
+ can't express (e.g. swap a nav for a menu button). `hide-below-X` hides
176
+ under breakpoint X; `hide-from-X` hides at X and up. `show-*` are the
177
+ readable inverse aliases (show-from-X ≡ hide-below-X, show-below-X ≡
178
+ hide-from-X) — same media rule, name whichever reads clearer at the site. */
179
+ @media (width <= 639.98px) { .juno-hide-below-sm, .juno-show-from-sm { display: none !important; } }
180
+
181
+ @media (width >= 640px) { .juno-hide-from-sm, .juno-show-below-sm { display: none !important; } }
182
+
183
+ @media (width <= 767.98px) { .juno-hide-below-md, .juno-show-from-md { display: none !important; } }
184
+
185
+ @media (width >= 768px) { .juno-hide-from-md, .juno-show-below-md { display: none !important; } }
186
+
187
+ @media (width <= 1023.98px) { .juno-hide-below-lg, .juno-show-from-lg { display: none !important; } }
98
188
 
99
- @media (width >= 768px) { .juno-hide-from-md { display: none !important; } }
189
+ @media (width >= 1024px) { .juno-hide-from-lg, .juno-show-below-lg { display: none !important; } }
@@ -40,8 +40,11 @@
40
40
  font-variant-numeric: tabular-nums;
41
41
  }
42
42
 
43
+ /* --juno-label-size: optional size override, set by an ancestor (e.g. a
44
+ * density wrapper or a "compact" section) — never declared here, so it
45
+ * isn't shadowed for descendants that also want to read it. */
43
46
  .juno-label {
44
- font-size: var(--juno-font-size-13);
47
+ font-size: var(--juno-label-size, var(--juno-font-size-13));
45
48
  color: var(--juno-label);
46
49
  }
47
50