@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
@@ -0,0 +1,215 @@
1
+ # Dock
2
+
3
+ The bottom navigation bar of the app shell on narrow viewports β€” 3–5 primary
4
+ destinations as icon-over-label tap targets. The phone-width counterpart of the
5
+ [rail](./rail.md); the swap recipe is in [layout.md](../layout.md#app-shell).
6
+
7
+ ## Web
8
+
9
+ ```html
10
+ <nav class="juno-dock" aria-label="Primary">
11
+ <a class="juno-dock__item" href="/library" aria-current="page">
12
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-squares-four" /></svg>
13
+ <span class="juno-dock__label">Library</span>
14
+ </a>
15
+ <a class="juno-dock__item" href="/nodes">
16
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-hexagon" /></svg>
17
+ <span class="juno-dock__label">Nodes</span>
18
+ </a>
19
+ </nav>
20
+ ```
21
+
22
+ | Class / prop | Effect |
23
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
24
+ | `.juno-dock` | Sticky bottom `s1` bar, 1px `border` seam on top, safe-area pad. |
25
+ | `.juno-dock__item` | Equal-width icon-over-label target, β‰₯ `size.tap.comfortable` tall. |
26
+ | `.juno-dock__label` | The text β€” truncates with an ellipsis, never wraps. |
27
+ | `[aria-current]` | Active item: `s2` fill + 2px role edge on top. Attribute, not class. |
28
+ | `.juno-dock--fixed` | Pin to the viewport foot (`position: fixed`) β€” for page-scroll shells where sticky won't pin. |
29
+ | `.juno-dock--pill` | Floating rounded pill: big glyphs in circular bubbles, labels hidden, active = bubble fill. |
30
+ | `.juno-dock--float` | Pillbar-style floating capsule chrome (fixed, rounded, blurred, shadowed) β€” keeps icon+label items and the full-item active fill. Combine with `--icon` for the `--pill` look, decomposed. |
31
+ | `.juno-dock--icon` | Labels hidden, glyphs grow, active = bubble fill β€” standalone (no floating chrome required). |
32
+ | `.juno-dock__bubble` | Circular icon holder (`--pill`/`--icon`); pair with `.juno-icon-loader` to host the loading ring. |
33
+ | `.juno-dock--collapsible` | Fold-to-a-circle variant driven by `--juno-dock-fold` β€” see [Collapsible](#collapsible-fold-driven). |
34
+ | `.juno-dock__tray` | The collapsible bar's item row β€” fades out as the fold closes. |
35
+ | `.juno-dock__knob` | The collapsed circle's face (a button); shown only under `data-juno-collapsed`. |
36
+ | `.juno--<role>` | Active-edge color (default `active`). |
37
+ | `--juno-dock-scale` | Consumer-set scale factor (default `1`) applied to the whole bar β€” e.g. shrink on scroll. |
38
+
39
+ ## Pill variant
40
+
41
+ `.juno-dock--pill` turns the full-width bar into a floating rounded pill: big
42
+ glyphs sit in circular `.juno-dock__bubble`s, the labels drop, and only the
43
+ active tab's bubble fills. It floats out of flow (fixed), so the page scrolls
44
+ _under_ it β€” reserve room on the scroller with
45
+ `padding-block-end: var(--juno-dock-clearance)` (a published token that folds
46
+ in the pill height + safe area, so it stays correct if the geometry changes).
47
+
48
+ ```html
49
+ <nav class="juno-dock juno-dock--pill juno-hide-from-md" aria-label="Primary">
50
+ <a class="juno-dock__item" href="/library" aria-current="page" aria-label="Library">
51
+ <span class="juno-dock__bubble juno-icon-loader">
52
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-squares-four" /></svg>
53
+ </span>
54
+ </a>
55
+ <button class="juno-dock__item" aria-label="Nodes">
56
+ <span class="juno-dock__bubble juno-icon-loader">
57
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-hexagon" /></svg>
58
+ </span>
59
+ </button>
60
+ </nav>
61
+ ```
62
+
63
+ - **Labels are hidden β€” every item MUST carry an `aria-label`** (on the
64
+ `<a>`/`<button>`). The glyph alone is not an accessible name.
65
+ - Focus lands on the bubble, so the ring hugs the round target.
66
+ - Pair every bubble with [`.juno-icon-loader`](./icon-loader.md) β€” it's the
67
+ concentric-stack contract, and having it on from the start means a loading
68
+ arc can appear later without touching the class list.
69
+
70
+ ### Section-loading ring
71
+
72
+ The bubble doesn't invent its own ring: it is an
73
+ [icon-loader](./icon-loader.md) host that overrides the ring's two dimensions
74
+ (`--juno-icon-loader-ring` = the bubble diameter, `--juno-icon-loader-ring-width`
75
+ = 2px), so the arc rings the bubble's **edge** rather than the glyph. Drop an
76
+ indeterminate [arc](./loader.md) inside while the section loads; it eats no
77
+ clicks, so the item still activates, and because the bubble's box is definite
78
+ the arc appearing never resizes it. The app owns the state β€” add/remove
79
+ `.juno-arc--indeterminate`; give the arc `role="status"` + `aria-label`.
80
+
81
+ ```html
82
+ <span class="juno-dock__bubble juno-icon-loader">
83
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-gear" /></svg>
84
+ <span class="juno-arc juno-arc--indeterminate" role="status" aria-label="Loading"></span>
85
+ </span>
86
+ ```
87
+
88
+ ## Float variant
89
+
90
+ `.juno-dock--float` lifts the pillbar's floating capsule chrome (fixed
91
+ position, rounded shell, blur, shadow, off-edge margin) onto the dock's own
92
+ item model β€” icon+label items stay, and the active item still fills (`s2` +
93
+ role edge), just clipped to the capsule's rounded corners. It's `--pill`'s
94
+ exterior without `--pill`'s interior; combine with `--icon` below to
95
+ reproduce the original `--pill` look from two composable pieces. Out of flow
96
+ (fixed), so reserve room on the scroller with
97
+ `padding-block-end: var(--juno-dock-clearance)`.
98
+
99
+ ```html
100
+ <nav class="juno-dock juno-dock--float juno-hide-from-md" aria-label="Primary">
101
+ <a class="juno-dock__item" href="/library" aria-current="page">
102
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-squares-four" /></svg>
103
+ <span class="juno-dock__label">Library</span>
104
+ </a>
105
+ <a class="juno-dock__item" href="/nodes">
106
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-hexagon" /></svg>
107
+ <span class="juno-dock__label">Nodes</span>
108
+ </a>
109
+ </nav>
110
+ ```
111
+
112
+ ## Icon variant
113
+
114
+ `.juno-dock--icon` hides labels, grows the glyph, and moves the active state
115
+ onto a circular `.juno-dock__bubble` β€” usable standalone on a plain
116
+ sticky/`--fixed` bar for a compact, icon-only dock that stays in flow. Follows
117
+ the same bubble contract as `--pill` (pair with
118
+ [`.juno-icon-loader`](./icon-loader.md); see the pill section above for the
119
+ section-loading ring).
120
+
121
+ ```html
122
+ <nav class="juno-dock juno-dock--icon" aria-label="Primary">
123
+ <a class="juno-dock__item" href="/library" aria-current="page" aria-label="Library">
124
+ <span class="juno-dock__bubble juno-icon-loader">
125
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-squares-four" /></svg>
126
+ </span>
127
+ </a>
128
+ <button class="juno-dock__item" aria-label="Nodes">
129
+ <span class="juno-dock__bubble juno-icon-loader">
130
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-hexagon" /></svg>
131
+ </span>
132
+ </button>
133
+ </nav>
134
+ ```
135
+
136
+ - **Labels are hidden β€” every item MUST carry an `aria-label`.**
137
+ - Combine with `.juno-dock--float` for a floating icon-only capsule (the
138
+ original `--pill` treatment, built from the two composable variants).
139
+
140
+ ## Shrink on scroll
141
+
142
+ `--juno-dock-scale` (default `1`) drives a `transform: scale()` on the whole
143
+ bar, anchored `bottom center`, so a consumer can compact the dock as the page
144
+ scrolls without junoui shipping any scroll-listener JS:
145
+
146
+ ```js
147
+ dockEl.style.setProperty('--juno-dock-scale', shrink ? '0.92' : '1');
148
+ ```
149
+
150
+ The transition duration is authored through the motion scale, so
151
+ `prefers-reduced-motion` collapses it to an instant snap with no
152
+ component-specific media query.
153
+
154
+ ## Collapsible (fold-driven)
155
+
156
+ `.juno-dock--collapsible` folds the whole bar into a single circular
157
+ `.juno-dock__knob` at the inline-end edge, driven by one inherited custom prop
158
+ the app writes β€” `--juno-dock-fold` (`0` open … `1` the circle). Write it per
159
+ scroll frame for a gesture-tracked fold, or flip it `0`/`1` for a toggle.
160
+ Compose with a fixed placement (`--pill`, `--float`, or `--fixed`).
161
+
162
+ ```html
163
+ <nav class="juno-dock juno-dock--pill juno-dock--collapsible" aria-label="Primary">
164
+ <div class="juno-dock__tray">…the usual __item children…</div>
165
+ <button class="juno-dock__knob" aria-label="Show navigation">
166
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-dots-three" /></svg>
167
+ </button>
168
+ </nav>
169
+ ```
170
+
171
+ Two phases, split at `--juno-dock-fold-split` (default `0.35`): first the bar
172
+ **shrinks** in place to `--juno-dock-fold-scale` (default `0.78`, transform
173
+ only β€” no relayout), then it **slides** shut to `--juno-dock-collapsed-size`.
174
+ The width interpolates between two definite lengths (a transition cannot run
175
+ to an intrinsic size); `--juno-dock-fold-smoothing` (default `90ms`) smooths
176
+ over scroll-event discretization β€” anything longer visibly lags the finger.
177
+
178
+ - `data-juno-collapsed` is the **end state**, set by the app only at fold = 1:
179
+ it hides the tray (`visibility` β€” the items leave the tab order) and reveals
180
+ the knob. Remove it the moment the fold reopens.
181
+ - The knob **must** carry an `aria-label`. Collapsing while focus is inside
182
+ the tray is the app's edge β€” move focus to the knob first.
183
+ - The fold's scale **replaces** the base `--juno-dock-scale` hook (one
184
+ transform slot). Transform-origin is the physical bottom right; RTL
185
+ consumers flip the origin and inset overrides together.
186
+ - Knobs: `--juno-dock-collapsed-size` (default: one tap-comfortable bubble
187
+ plus the pill's padding and border), `--juno-dock-edge-gap` (what the
188
+ floating margins take from `100%` β€” default `2 Γ— space.12`).
189
+
190
+ ## Anatomy (any platform)
191
+
192
+ - Full-width bar on `s1`, hairline seam on the block-start edge; items split the
193
+ width evenly, icon above a `font.size.10` uppercase label.
194
+ - Active = `s2` fill + `border.width.2` block-start edge in the role color β€”
195
+ the rail's active language rotated 90Β°.
196
+ - Bottom padding extends into the safe area (`env(safe-area-inset-bottom)`)
197
+ so the home indicator never covers a target.
198
+
199
+ ## Usage
200
+
201
+ - Keep it to 3–5 destinations; overflow belongs in a "More" item opening a
202
+ [drawer](./drawer.md) or [menu](./menu.md).
203
+ - Sticky, not fixed: place it last inside the scrolling column and it pins
204
+ itself without overlapping content (no bottom-padding hacks). Best is the
205
+ [`.juno-app-shell`](../layout.md#app-shell) frame, whose `__main` region is
206
+ the scroller β€” the dock then sits at the body foot in flow.
207
+ - **Short-page caveat:** sticky only pins while the column overflows. If the
208
+ _whole page_ scrolls and content is shorter than the viewport, the dock
209
+ lands mid-content. For that layout use `.juno-dock--fixed` (viewport-pinned)
210
+ and reserve its height at the page foot so it doesn't cover the last row.
211
+ - Pair with the rail via the viewport helpers: `.juno-hide-below-md` on the
212
+ rail, `.juno-hide-from-md` on the dock. One nav is hidden from the
213
+ accessibility tree at a time, so both may share `aria-label="Primary"`.
214
+ - The app sets `aria-current="page"` on the active item β€” junoui styles the
215
+ attribute so nav semantics stay honest.
@@ -0,0 +1,100 @@
1
+ # Drawer
2
+
3
+ An edge-anchored `<dialog>` for secondary tasks and settings β€” same scrim and layer
4
+ as the [modal](./modal.md), but it slides along one axis. Reuses `.juno-modal` for the
5
+ surface; `.juno-drawer` re-pins and re-slides it.
6
+
7
+ ## Web
8
+
9
+ ```html
10
+ <dialog class="juno-modal juno-drawer">
11
+ <!-- header / body / footer, same parts as a modal -->
12
+ </dialog>
13
+
14
+ <dialog class="juno-modal juno-drawer juno-drawer--start">…</dialog>
15
+ <dialog class="juno-modal juno-drawer juno-drawer--bottom">…</dialog>
16
+ ```
17
+
18
+ | Class | Effect |
19
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
20
+ | `.juno-drawer` | 380px, full height, pinned to the **inline-end** edge. |
21
+ | `.juno-drawer--start` | Pins to the inline-start edge. |
22
+ | `.juno-drawer--bottom` | Full width, real bottom sheet: height via `--juno-sheet-h` (default `60dvh`), grab handle, rounded top corners, pinned to the bottom edge. |
23
+
24
+ ## Anatomy (any platform)
25
+
26
+ - Slide in from its edge, `motion.duration.base` (200ms) / `ease.decel`. Same scrim
27
+ (`opacity.scrim`) and shadow (`shadow.3`) as the modal.
28
+ - Edge anchoring is logical (`margin-inline`), so start/end mirror under RTL.
29
+
30
+ ## Bottom sheet (`.juno-drawer--bottom`)
31
+
32
+ ```html
33
+ <dialog class="juno-modal juno-drawer juno-drawer--bottom" aria-labelledby="sheet-tag">
34
+ <div class="juno-sheet__handle" aria-hidden="true"></div>
35
+ <div class="juno-modal__stripe"></div>
36
+ <div class="juno-modal__head">
37
+ <span class="juno-modal__tag" id="sheet-tag">FILTERS</span>
38
+ <button class="juno-modal__close" autofocus aria-label="Close">βœ•</button>
39
+ </div>
40
+ <div class="juno-modal__body"><!-- scrolls on its own, pads past the home indicator --></div>
41
+ </dialog>
42
+ ```
43
+
44
+ - **Height knob:** `--juno-sheet-h` (default `60dvh`) sets the sheet's block-size;
45
+ `--juno-sheet-max` (default `92dvh`) caps it. Both are plain custom properties an
46
+ app can swap per open (or per breakpoint) for peek/half/full snap points β€” this is
47
+ CSS only. junoui does **not** ship the drag gesture that would let a user pull
48
+ between those points; that's stateful interaction and belongs to the app (or a
49
+ sibling `junoui-<framework>` package).
50
+ - **Grab handle:** `.juno-sheet__handle` is a decorative bar the app renders as the
51
+ first child. Mark it `aria-hidden="true"` β€” the dialog's own `aria-labelledby`
52
+ (pointing at the title) remains the accessible name. It is not a button and has no
53
+ built-in drag behavior.
54
+ - **Safe area lives on the body, not the sheet.** `.juno-drawer--bottom` itself has
55
+ `padding-block-end: 0`; `.juno-modal__body` carries
56
+ `calc(var(--juno-pad-surface-inline) + env(safe-area-inset-bottom, 0px))` so a
57
+ scrolling sheet never hides its last row under the home indicator (a sheet that
58
+ doesn't scroll still gets the clearance, since the padding sits on the body box
59
+ either way).
60
+ - **`<dialog>` is the only supported sheet root.** `showModal()` gives you the
61
+ top-layer stacking, `::backdrop` scrim, ESC handling, scroll lock, and β€” critically β€”
62
+ focus trap **and** `inert` background for free: the rest of the page becomes
63
+ unfocusable and unclickable the moment the sheet opens, with no extra markup.
64
+ Hand-rolling a bottom sheet from a positioned `<div>` + a manual scrim `<button>`
65
+ reproduces the visual but silently drops all of that: no focus trap, no `inert`,
66
+ ESC and outside-dismiss have to be wired by hand and are easy to get wrong. Don't
67
+ do it β€” use `<dialog class="juno-modal juno-drawer juno-drawer--bottom">`.
68
+
69
+ ## The slide-over pattern
70
+
71
+ The full production composition β€” scrim, end-anchored panel on `s1`, header row,
72
+ scrolling body, pinned footer β€” assembled from existing parts. Copy-paste:
73
+
74
+ ```html
75
+ <dialog class="juno-modal juno-drawer" aria-labelledby="tray-tag">
76
+ <div class="juno-modal__stripe"></div>
77
+ <div class="juno-modal__head">
78
+ <span class="juno-modal__tag" id="tray-tag">ACTIVE JOBS</span>
79
+ <button class="juno-modal__close" autofocus aria-label="Close">βœ•</button>
80
+ </div>
81
+ <div class="juno-modal__body"><!-- scrolls on its own --></div>
82
+ <div class="juno-modal__foot">
83
+ <button class="juno-btn juno-btn--ghost">CLEAR DONE</button>
84
+ <button class="juno-btn juno--warning">PAUSE ALL</button>
85
+ </div>
86
+ </dialog>
87
+ ```
88
+
89
+ `showModal()` provides the scrim (`::backdrop`, `opacity.scrim`) and z-order for
90
+ free β€” no scrim element, no z-index bookkeeping. Head/body/foot are the modal
91
+ parts; the drawer class only re-pins and re-slides the surface.
92
+
93
+ ## Usage
94
+
95
+ - Settings panels, filters, detail editors that don't warrant leaving context.
96
+ - Same `<dialog>` + `showModal()` contract as the modal.
97
+ - **Phone widths (< `bp.sm`) are automatic:** side drawers cap at `85vw` so a
98
+ sliver of scrim stays visible (and tappable) for dismissal. The bottom drawer's
99
+ body always pads past the home indicator (`safe-area-inset-bottom`) β€” see
100
+ [Bottom sheet](#bottom-sheet-juno-drawer--bottom) above.
@@ -0,0 +1,44 @@
1
+ # Field
2
+
3
+ Wraps any control with a label, optional help text, and an error message. Pure
4
+ layout + the validation/required cue β€” it owns no state.
5
+
6
+ ## Web
7
+
8
+ ```html
9
+ <div class="juno-field">
10
+ <label class="juno-field__label" for="cs">
11
+ Order ID <span class="juno-field__req" aria-hidden="true">*</span>
12
+ </label>
13
+ <input id="cs" class="juno-input juno-mono" type="text" aria-required="true" />
14
+ <span class="juno-field__help">Prefix + number</span>
15
+ </div>
16
+
17
+ <!-- error -->
18
+ <div class="juno-field">
19
+ <label class="juno-field__label" for="sq">Error code</label>
20
+ <input id="sq" class="juno-input juno-mono" aria-invalid="true" aria-describedby="sq-e" />
21
+ <span class="juno-field__error" id="sq-e">Service unavailable</span>
22
+ </div>
23
+ ```
24
+
25
+ | Class | Effect |
26
+ | -------------------- | ----------------------------------------------------------------------- |
27
+ | `.juno-field` | Vertical stack (label Β· control Β· help/error), `space.4` gap. |
28
+ | `.juno-field__label` | 10px uppercase, tracking `wider`, `label` color. |
29
+ | `.juno-field__req` | Required asterisk in `warning`. Decorative β€” pair with `aria-required`. |
30
+ | `.juno-field__help` | 11px `label` helper text. |
31
+ | `.juno-field__error` | 11px `warning` error text. |
32
+
33
+ ## Anatomy (any platform)
34
+
35
+ - Stack label β†’ control β†’ message; gap `space.4`.
36
+ - Label: B612, `font.size.10`, uppercase, tracking `font.tracking.wider`, `label` color.
37
+ - Help `font.size.11` `label`; error `font.size.11` `warning`.
38
+
39
+ ## Usage
40
+
41
+ - One control per field. Tie `for`/`id`, and `aria-describedby` to the help/error id.
42
+ - For errors, set `aria-invalid="true"` on the control (it re-colors its own border)
43
+ **and** show `.juno-field__error`. The asterisk is visual; required-ness is
44
+ `aria-required` on the control.
@@ -0,0 +1,31 @@
1
+ # Fold slot
2
+
3
+ Animated presence for a member of any flex/grid row: instead of popping in and
4
+ out of the DOM, the slot's width folds to zero, it fades, and at the end of the
5
+ fold it leaves the tab order (`visibility`, discretely transitioned β€” instant
6
+ coming in, end-of-fade going out). Keep the element mounted; flip
7
+ `data-juno-in`. Zero JS.
8
+
9
+ ## Web
10
+
11
+ ```html
12
+ <div class="juno-pillbar">
13
+ <button class="juno-fold" data-juno-in aria-label="Scroll to top">
14
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-arrow-up" /></svg>
15
+ </button>
16
+ …other members…
17
+ </div>
18
+ ```
19
+
20
+ | Class / prop | Effect |
21
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
22
+ | `.juno-fold` | The slot: definite width (`--juno-fold-size`), folds shut when `data-juno-in` is absent. |
23
+ | `[data-juno-in]` | Present state β€” app-set. Absent = folded away, invisible, out of the tab order. |
24
+ | `--juno-fold-size` | The slot's open width (default `size.tap.comfortable`) β€” must be definite, transitions cannot run to an intrinsic size. |
25
+ | `--juno-fold-gap` | The row's flex/grid gap (default `0px`) β€” the folded slot swallows one gap with a negative margin so the row closes completely. |
26
+
27
+ - The row's own width follows the slot, so the whole row slides open/closed.
28
+ - `prefers-reduced-motion`: handled by the base layer (states still apply,
29
+ transition durations collapse).
30
+ - Canonical use: transient toolbar actions β€” a scroll-to-top arrow, a
31
+ contextual button β€” whose arrival should slide the row rather than jump it.
@@ -0,0 +1,48 @@
1
+ # Gauge
2
+
3
+ A determinate metric ring β€” CPU / RAM / DISK readouts with the value centered.
4
+ The metric cousin of the [arc loader](./loader.md): the arc shows _progress of a
5
+ task_; the gauge shows _the current level of a quantity_. Pure CSS
6
+ (`conic-gradient`), no SVG in the markup.
7
+
8
+ ## Web
9
+
10
+ ```html
11
+ <div
12
+ class="juno-gauge juno-gauge--sm juno--nominal"
13
+ style="--juno-gauge-value: 67"
14
+ role="meter"
15
+ aria-valuenow="67"
16
+ aria-valuemin="0"
17
+ aria-valuemax="100"
18
+ aria-label="Disk"
19
+ >
20
+ <span class="juno-gauge__value">67</span>
21
+ </div>
22
+ <span class="juno-gauge__label">DISK</span>
23
+ ```
24
+
25
+ | Class / prop | Effect |
26
+ | -------------------- | ----------------------------------------------------------------------------- |
27
+ | `.juno-gauge` | 64px ring; sweep = `--juno-gauge-value` (0–100). |
28
+ | `.juno-gauge--sm` | 44px β€” dense card grids. |
29
+ | `.juno-gauge--lg` | 96px β€” hero metrics. |
30
+ | `.juno-gauge__value` | Centered mono readout, role-colored. |
31
+ | `.juno-gauge__label` | Caption (a sibling β€” place it under or beside the ring). |
32
+ | `.juno--<role>` | Ring + value color (default `active`). |
33
+ | `--juno-gauge-value` | The value, set inline or from JS. Registered (`@property`) so it transitions. |
34
+
35
+ ## Anatomy (any platform)
36
+
37
+ - Ring: track = `border` color, fill sweep = role, round the ring `size / 16`
38
+ stroke (4px at 64). Value: mono, bold, same color as the sweep.
39
+ - Sweep starts at 12 o'clock, clockwise.
40
+
41
+ ## Usage
42
+
43
+ - **Thresholds are app policy.** junoui ships no value β†’ color rule; map it
44
+ yourself. The field-tested recipe: `v β‰₯ 90 β†’ warning`, `β‰₯ 75 β†’ caution`,
45
+ `else nominal`.
46
+ - Add `role="meter"` + `aria-valuenow/min/max` + a label; the ring alone is
47
+ invisible to assistive tech.
48
+ - For task progress (this will finish), use a [loader](./loader.md) instead.
@@ -0,0 +1,122 @@
1
+ # Icon loader
2
+
3
+ A control ringed by the spinning [arc](./loader.md#arc) while its section
4
+ loads β€” the "this section is loading" affordance for a [rail](./rail.md),
5
+ [dock](./dock.md), or [pillbar](./pillbar.md) item. What it wraps stays static
6
+ on top; the arc rings it and takes no pointer events, so the item still clicks
7
+ through. Zero JS in the CSS β€” the app toggles the arc's `--indeterminate` class
8
+ to start/stop the spin.
9
+
10
+ This is junoui's **only** concentric-ring mechanism. Anything that wants an arc
11
+ around it composes `.juno-icon-loader` rather than re-rolling the geometry β€” the
12
+ [dock](./dock.md#section-loading-ring)'s `__bubble` is the same wrapper with the
13
+ ring resized to the bubble.
14
+
15
+ ## Web
16
+
17
+ ```html
18
+ <a class="juno-rail__item" href="/library" aria-current="page">
19
+ <span class="juno-icon-loader">
20
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-squares-four" /></svg>
21
+ <span class="juno-arc juno-arc--indeterminate" role="status" aria-label="Loading"></span>
22
+ </span>
23
+ <span class="juno-rail__label">Library</span>
24
+ </a>
25
+ ```
26
+
27
+ When the section finishes, remove `.juno-arc--indeterminate` (or the whole
28
+ `.juno-arc`). For a progress ring instead of an endless spin, drop
29
+ `--indeterminate` and set `--juno-progress` (0–100) on the `.juno-arc`.
30
+
31
+ | Class / prop | Effect |
32
+ | ------------------------------- | ------------------------------------------------------------------- |
33
+ | `.juno-icon-loader` | Single-cell grid wrapper β€” every child is centred on the same cell. |
34
+ | `> *` | The ringed content, held above the ring. |
35
+ | `> .juno-arc` | The ring β€” pointer-transparent, painted under the content. |
36
+ | `--juno-icon-loader-ring` | Ring diameter (default `1.9em`, i.e. relative to the glyph). |
37
+ | `--juno-icon-loader-ring-width` | Ring stroke (default `0.14em`). |
38
+ | `.juno--<role>` | Ring color (default `active`). |
39
+
40
+ ### Ringing something bigger than a glyph
41
+
42
+ Both dimensions are custom props, so the same wrapper rings a 14px glyph and a
43
+ 44px bubble. Give the host a **definite** box and set the diameter to it β€” then
44
+ the ring hugs the edge and the box does not resize when the arc appears:
45
+
46
+ ```css
47
+ .my-avatar-loader {
48
+ --juno-icon-loader-ring: var(--juno-size-tap-comfortable);
49
+ --juno-icon-loader-ring-width: 2px;
50
+
51
+ inline-size: var(--juno-size-tap-comfortable);
52
+ block-size: var(--juno-size-tap-comfortable);
53
+ }
54
+ ```
55
+
56
+ That is exactly how `.juno-dock__bubble` gets its section-loading ring.
57
+
58
+ ### Ringing an arbitrary control (button, badge, avatar)
59
+
60
+ The wrapper doesn't care what it wraps β€” swap the icon for any interactive
61
+ control and the ring still hugs it without resizing its box. Set the ring
62
+ diameter to the control's own size (its `--juno-size-*` or `em` value) and
63
+ the control keeps its own footprint; the wrapper is the only thing that
64
+ grows to fit the ring:
65
+
66
+ ```html
67
+ <!-- 40px circular icon button -->
68
+ <span
69
+ class="juno-icon-loader"
70
+ style="--juno-icon-loader-ring:40px;--juno-icon-loader-ring-width:2px"
71
+ >
72
+ <button class="juno-btn juno-btn--icon" aria-label="More">&#8943;</button>
73
+ <span class="juno-arc juno-arc--indeterminate" role="status" aria-label="Loading"></span>
74
+ </span>
75
+
76
+ <!-- 14px inline badge β€” small ring, use --smooth to avoid stepped jitter -->
77
+ <span
78
+ class="juno-icon-loader"
79
+ style="--juno-icon-loader-ring:14px;--juno-icon-loader-ring-width:2px"
80
+ >
81
+ <span class="juno-badge juno-badge--micro">3</span>
82
+ <span
83
+ class="juno-arc juno-arc--indeterminate juno-arc--smooth"
84
+ role="status"
85
+ aria-label="Loading"
86
+ ></span>
87
+ </span>
88
+ ```
89
+
90
+ The ring is `pointer-events: none` and sits under the content (`z-index: 0`
91
+ vs. `1`), so a wrapped `<button>` stays fully clickable.
92
+
93
+ ## Anatomy (any platform)
94
+
95
+ - A wrapper that stacks the ring and what it rings on one centred cell; with no
96
+ explicit size the wrapper sizes to the larger of the two (normally the ring).
97
+ - The ring color is the semantic role; a `.juno--loading`/`.juno--active`
98
+ ancestor tints it.
99
+
100
+ ## Usage
101
+
102
+ - **Concentric via a single-cell grid, never `translate(-50%, -50%)`** β€” every
103
+ child shares one grid cell (`place-items: center`), so they're centered on
104
+ each other without any `transform`. The arc's rotation animation writes
105
+ `transform`, so a translate-based centering would be clobbered; keep the grid
106
+ approach if you re-roll the markup.
107
+ - **Don't hand-roll a second ring.** If a component needs an arc around it,
108
+ compose this class and override the two ring props β€” one primitive keeps the
109
+ centring gotcha, the pointer-events rule, and the paint order in one place.
110
+ That includes bespoke overlay CSS elsewhere for a "this control is loading"
111
+ ring β€” delete it and wrap the control in `.juno-icon-loader` instead.
112
+ - Below ~24px, add `.juno-arc--smooth` next to `--indeterminate` β€” the
113
+ default 12-step sweep (see [loader](./loader.md)) reads as jitter at that
114
+ size; `--smooth` makes it a continuous rotation.
115
+ - Gate the spin to first load, not every background refetch β€” a nav icon that
116
+ blinks on every poll reads as broken. Add `--indeterminate` when a section
117
+ has no data yet; remove it once loaded.
118
+ - The arc carries the loading semantics (`role="status"` +
119
+ `aria-label="Loading"`); the icon stays `aria-hidden`. Don't also mark the
120
+ nav item busy β€” one announcement is enough.
121
+ - Respects reduced motion via the arc's own stepped, low-key animation; see
122
+ [loader](./loader.md).
@@ -0,0 +1,70 @@
1
+ # Icon
2
+
3
+ A zero-JS inline-SVG glyph. Icons ship as one **SVG sprite** of `<symbol>`s; you
4
+ reference a symbol with `<use>`. The glyph inherits the current text color and sizes
5
+ in `em`, so it tracks `font-size` and the `[data-juno-text]` scale.
6
+
7
+ Set ships [Phosphor Icons](https://phosphoricons.com) (bold weight) β€” MIT-licensed,
8
+ free to redistribute and sell. The bundle carries the license in `src/icons/LICENSE`
9
+ and a banner in the generated sprite; keep both when you ship.
10
+
11
+ ## Web
12
+
13
+ ```html
14
+ <!-- decorative β€” hide from assistive tech -->
15
+ <svg class="juno-icon" aria-hidden="true">
16
+ <use href="node_modules/junoui/dist/icons/juno-icons.svg#juno-i-gear" />
17
+ </svg>
18
+
19
+ <!-- meaningful β€” label it -->
20
+ <svg class="juno-icon juno-icon--lg juno-icon--role juno--warning" role="img" aria-label="Warning">
21
+ <use href="…/juno-icons.svg#juno-i-warning" />
22
+ </svg>
23
+ ```
24
+
25
+ Import path: `@junoput01/junoui/icons` resolves to the sprite.
26
+
27
+ ## Same-document refs (Safari fix)
28
+
29
+ An **external** sprite reference (`<use href="…file.svg#id">`) intermittently
30
+ fails to render in Safari β€” icons randomly vanish and reappear. The reliable
31
+ path is a **same-document** reference (`<use href="#juno-i-gear">`), which needs
32
+ the sprite living in the current document. Rather than hand-roll that injection
33
+ in every app, import the shipped helper once:
34
+
35
+ ```js
36
+ import '@junoput01/junoui/icons/inline'; // injects the sprite into the document once
37
+ ```
38
+
39
+ ```html
40
+ <svg class="juno-icon" aria-hidden="true"><use href="#juno-i-gear" /></svg>
41
+ ```
42
+
43
+ The module auto-installs on import in a browser (no-op server-side / before
44
+ hydration) and is id-guarded, so importing it from many modules injects only
45
+ once. It also exports `installJunoIcons(doc)` (default + named) for manual or
46
+ multi-document (iframe) control. No bundler `?raw` loader needed.
47
+
48
+ | Class | Effect |
49
+ | ------------------ | ------------------------------------------------------------- |
50
+ | `.juno-icon` | 1.25em square, `fill: currentColor`, baseline-aligned inline. |
51
+ | `.juno-icon--sm` | 1em (matches surrounding text). |
52
+ | `.juno-icon--lg` | 1.75em. |
53
+ | `.juno-icon--xl` | 2.5em. |
54
+ | `.juno-icon--role` | Tint with `var(--juno-role)` instead of inherited text color. |
55
+ | `--juno-icon-size` | Override the size directly (any length). |
56
+
57
+ ## Sizing & color
58
+
59
+ - **Color** is `currentColor`: an icon inside colored text matches it. Add
60
+ `.juno-icon--role` + a `.juno--<role>` ancestor (or set `--juno-role`) to tint by
61
+ status. Always pair a status color with a text label β€” color is never the only signal.
62
+ - **Size** is `em`-relative, so icons grow with the type scale and stay proportional
63
+ inside buttons / inputs. For a fixed pixel size set `--juno-icon-size: 20px`.
64
+
65
+ ## Adding / changing icons
66
+
67
+ Drop an SVG into `src/icons/` (must use `currentColor`, a `viewBox`), then
68
+ `npm run build:icons` (part of `npm run build`). The file name becomes the symbol id
69
+ `juno-i-<name>`. Swapping icon sets is a source change in `src/icons/` β€” keep the new
70
+ set's license file alongside.