@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,211 @@
1
+ # Pillbar
2
+
3
+ A floating pill-shaped bar hovering above the content's bottom edge — the
4
+ iOS-style alternative to the full-width [dock](./dock.md). Holds 2–5 icon
5
+ actions or destinations; mix nav links and toggle buttons, separated by an
6
+ optional divider. Zero JS.
7
+
8
+ ## Web
9
+
10
+ ```html
11
+ <nav class="juno-pillbar" aria-label="Primary">
12
+ <a class="juno-pillbar__item" href="/library" aria-current="page" aria-label="Library">
13
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-squares-four" /></svg>
14
+ <span class="juno-pillbar__label">Library</span>
15
+ </a>
16
+ <a class="juno-pillbar__item" href="/albums" aria-label="Albums">
17
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-images" /></svg>
18
+ </a>
19
+ <span class="juno-pillbar__sep"></span>
20
+ <button class="juno-pillbar__item" aria-label="Sync">
21
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-arrows-clockwise" /></svg>
22
+ </button>
23
+ </nav>
24
+ ```
25
+
26
+ | Class / prop | Effect |
27
+ | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
28
+ | `.juno-pillbar` | Sticky floating pill: blurred translucent `s1`, hairline, `shadow.2`. |
29
+ | `.juno-pillbar__item` | Round tap target ≥ `size.tap.comfortable`; icon with optional label. |
30
+ | `.juno-pillbar__label` | Inline text next to the icon — truncates at 12ch. |
31
+ | `.juno-pillbar__sep` | Vertical hairline between item groups (e.g. destinations vs. actions). |
32
+ | `[aria-current]` | Active destination: `s3` pill fill + role color. Attribute, not class. |
33
+ | `[aria-pressed]` | Same active look for toggle buttons. |
34
+ | `.juno-pillbar--fixed` | Fix to the viewport (`position: fixed`), floating its usual `space.16` + safe-area above the foot — for page-scroll shells where sticky won't pin. |
35
+ | `.juno-pillbar--top-right` / `--top-left` / `--bottom-right` / `--bottom-left` | Fix as a floating cluster in one viewport corner (safe-area-clamped), instead of the centered bottom bar. |
36
+ | `.juno-pillbar__input` | Borderless search field inside the pill (`min(52vw, 240px)`, 16px font floor). |
37
+ | `.juno-pillbar__overflow` | "More" trigger for items that don't fit — anchors a [menu](./menu.md) via `popovertarget`. |
38
+ | `.juno-pillbar--collapsible` | The pill folds into a single circle (`__toggle`) and animates back to full width — see [Collapsible](#collapsible). |
39
+ | `.juno-pillbar__toggle` | The collapsible pill's circular expand/collapse control; state on `aria-expanded`, same chrome as `__overflow`. |
40
+ | `.juno-pillbar__tray` | Wrapper whose width animates shut — holds one child wrapping the usual `__item` markup. |
41
+ | `.juno--<role>` | Active color (default `active`). |
42
+
43
+ ### Geometry custom props
44
+
45
+ Read from `.juno-pillbar` (or its computed style) instead of hardcoding these
46
+ in an app-side capacity planner — they're the single source of truth for how
47
+ many `__item` fit before the rest should route to `__overflow`.
48
+
49
+ | Prop | Default | Meaning |
50
+ | --------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
51
+ | `--juno-pillbar-item` | `size.tap.comfortable` | Item tap-target side (square). |
52
+ | `--juno-pillbar-gap` | `space.2` | Gap between items. |
53
+ | `--juno-pillbar-pad` | `space.4` | Pill's own inner padding. |
54
+ | `--juno-pillbar-edge` | `space.16` | Base (bottom-center) placement's offset from the viewport edge. Not used by the corner modifiers, which keep their own distinct block/inline offsets. |
55
+
56
+ ## Corner placement
57
+
58
+ The base pillbar is a centered bottom bar. The corner modifiers pin it as a
59
+ floating cluster in one viewport corner instead — e.g. a top-right
60
+ search/filter cluster over a full-bleed grid. Each flips to `position: fixed`
61
+ and clamps the corner with `env(safe-area-inset-*)` so it never lands under a
62
+ notch or the home indicator; the base blur/border/shadow carries over. For the
63
+ bottom corners, reserve scroll clearance with
64
+ `padding-block-end: var(--juno-pillbar-clearance)` if the pill overlaps
65
+ content.
66
+
67
+ ## Input slot
68
+
69
+ `.juno-pillbar__input` is an expandable search field that lives inside the
70
+ pill, reading as part of it rather than a boxed control.
71
+
72
+ ```html
73
+ <div class="juno-pillbar juno-pillbar--top-right">
74
+ <input
75
+ class="juno-pillbar__input"
76
+ type="search"
77
+ aria-label="Search library"
78
+ placeholder="Search…"
79
+ />
80
+ <button class="juno-pillbar__item" aria-label="Search" aria-pressed="true">
81
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-magnifying-glass" /></svg>
82
+ </button>
83
+ </div>
84
+ ```
85
+
86
+ - The field is borderless/transparent; its font-size is held at
87
+ `max(16px, …)` because iOS Safari zooms the whole page onto any focused text
88
+ field under 16px (the same floor the base [input](./input.md) applies on
89
+ touch).
90
+ - **A placeholder is not a label** — give the input an `aria-label` or a
91
+ visually-hidden `<label>`.
92
+
93
+ ## Overflow slot
94
+
95
+ `.juno-pillbar__overflow` is a "more" trigger, styled like `__item`, for
96
+ pills with variable membership (e.g. a top-right action pill whose item count
97
+ depends on the current view). It opens a [`.juno-menu`](./menu.md) holding
98
+ whatever didn't fit — zero JS, via the native Popover API:
99
+
100
+ ```html
101
+ <div class="juno-pillbar juno-pillbar--top-right">
102
+ <button class="juno-pillbar__item" aria-label="Search" aria-pressed="true">
103
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-magnifying-glass" /></svg>
104
+ </button>
105
+ <button class="juno-pillbar__item" aria-label="Filter">
106
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-funnel" /></svg>
107
+ </button>
108
+ <button
109
+ class="juno-pillbar__overflow"
110
+ popovertarget="pillbar-more"
111
+ aria-haspopup="menu"
112
+ aria-expanded="false"
113
+ aria-label="More"
114
+ >
115
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-dots-three" /></svg>
116
+ </button>
117
+ </div>
118
+ <ul class="juno-menu" id="pillbar-more" popover role="menu">
119
+ <li>
120
+ <button
121
+ class="juno-menu__item"
122
+ role="menuitem"
123
+ popovertarget="pillbar-more"
124
+ popovertargetaction="hide"
125
+ >
126
+ <span class="juno-menu__icon">⤓</span>Export
127
+ </button>
128
+ </li>
129
+ </ul>
130
+ ```
131
+
132
+ - **junoui ships the docking point, not the collapse policy.** Deciding
133
+ _which_ items overflow and _when_ is the app's job — read the
134
+ [geometry custom props](#geometry-custom-props) to do the capacity math,
135
+ then conditionally render `.juno-pillbar__overflow` and move the spilled
136
+ `__item`s into the menu. This keeps one source of truth for the pixel
137
+ constants instead of an app re-declaring them in JS.
138
+ - The invoker (`popovertarget`) is the menu's implicit anchor, so
139
+ `.juno-menu`'s own `position-try-fallbacks` handles flipping it clear of the
140
+ viewport edge — no extra positioning needed in the pill.
141
+ - `aria-expanded` mirrors the menu's open state like `[aria-pressed]` does for
142
+ toggle items; toggle it from the app or a stateless enhancer (junoui itself
143
+ ships no JS).
144
+
145
+ ## Collapsible
146
+
147
+ `.juno-pillbar--collapsible` lets the whole pill fold into a single circular
148
+ button and expand back to full width on demand — for toolbars that should get
149
+ out of the content's way (e.g. a media viewer's action bar). State lives on
150
+ the toggle's `aria-expanded`; junoui ships no JS, the app flips the attribute.
151
+
152
+ ```html
153
+ <nav class="juno-pillbar juno-pillbar--collapsible" aria-label="Tools">
154
+ <button class="juno-pillbar__toggle" aria-expanded="false" aria-label="Show toolbar">
155
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-dots-three" /></svg>
156
+ </button>
157
+ <div class="juno-pillbar__tray">
158
+ <div>
159
+ <button class="juno-pillbar__item" aria-label="Share">…</button>
160
+ <span class="juno-pillbar__sep"></span>
161
+ <button class="juno-pillbar__item" aria-label="Delete">…</button>
162
+ </div>
163
+ </div>
164
+ </nav>
165
+ ```
166
+
167
+ - **The tray needs exactly one child wrapper** (any element). The expansion is
168
+ a `grid-template-columns: 0fr ↔ 1fr` transition — the only widely-supported
169
+ way to animate to an intrinsic width (Safari 16+) — and the track needs a
170
+ single shrinkable child to collapse.
171
+ - DOM order is free: toggle-first reads naturally for a left-anchored pill,
172
+ toggle-last for a right-anchored corner (`--bottom-right`), so the circle
173
+ stays put and the tray grows out of it.
174
+ - Collapsed, the tray goes `visibility: hidden` at the end of the slide, which
175
+ also removes its items from the tab order. **Collapsing while focus is
176
+ inside the tray is the app's edge:** move focus to the toggle first.
177
+ - The toggle's glyph rotates a quarter-turn while expanded; swap the icon from
178
+ the app instead if you want e.g. dots → ×.
179
+ - `prefers-reduced-motion` is handled by the base layer (every transition
180
+ collapses to ~0ms) — the states still apply, just without the slide.
181
+
182
+ ## Anatomy (any platform)
183
+
184
+ - Fully-rounded capsule floating `space.16` off the bottom edge (plus the safe
185
+ area), centered inline; translucent `s1` with a background blur, hairline
186
+ `border`, `shadow.2` lift.
187
+ - Items are capsule targets at least `size.tap.comfortable` square; the active
188
+ one fills `s3` and takes the role color.
189
+ - A separator is a `border`-colored vertical hairline, `space.20` tall.
190
+
191
+ ## Usage
192
+
193
+ - Same rules as the dock: 3–5 destinations, overflow goes behind a "More" item.
194
+ Pick **one** bottom pattern per screen — dock _or_ pillbar, never both.
195
+ - Variable-membership action pills (not fixed destinations) use the
196
+ [overflow slot](#overflow-slot) instead of hand-rolling a "more" menu.
197
+ - Sticky, not fixed: place it last inside the scrolling column; it floats over
198
+ content while staying in flow. Best inside the
199
+ [`.juno-app-shell`](../layout.md#app-shell) frame, whose `__main` is the
200
+ scroller.
201
+ - **Short-page caveat:** sticky only pins while the column overflows, so on a
202
+ short page that doesn't scroll the pillbar lands mid-content. If the _whole
203
+ page_ scrolls, use `.juno-pillbar--fixed` to fix it to the viewport (it keeps
204
+ floating its usual gap above the foot) and reserve its footprint at the page
205
+ foot.
206
+ - Icon-only items need `aria-label`. Show the label on the active item (or all
207
+ items) when space allows.
208
+ - Nav destinations are `<a>` with `aria-current="page"`; momentary or toggle
209
+ actions are `<button>` (toggles flip `aria-pressed`).
210
+ - Works inside a [navbar](./navbar.md) + tab-stack shell — the recipe is in
211
+ [layout.md](../layout.md#app-shell).
@@ -0,0 +1,43 @@
1
+ # Popover
2
+
3
+ An anchored, click-triggered surface for rich, interactive content — filters,
4
+ quick-edits, detail cards. Unlike a [tooltip](./tooltip.md) it holds focusable controls
5
+ and persists until dismissed.
6
+
7
+ Built on the **native Popover API**: the panel lives in the browser **top layer**, so
8
+ it is _never_ clipped by an ancestor's `overflow`/`transform` and never fights
9
+ `z-index`. A `popovertarget` button is its **implicit anchor**; CSS anchor positioning
10
+ pins the panel below, inline-end aligned. Open/close, light-dismiss (outside click),
11
+ and ESC are all the platform's job — **zero JS**.
12
+
13
+ ## Web
14
+
15
+ ```html
16
+ <button popovertarget="pf" aria-haspopup="dialog">RANGE FILTER ▾</button>
17
+ <div class="juno-popover" id="pf" popover role="dialog" aria-label="Range filter">
18
+ <!-- any controls -->
19
+ <span class="juno-popover__arrow"></span>
20
+ </div>
21
+ ```
22
+
23
+ | Class | Effect |
24
+ | ---------------------- | ------------------------------------------------------------------- |
25
+ | `.juno-popover` | 280px `s2` panel, radius `5`, shadow `2`. Top-layer, anchor-pinned. |
26
+ | `.juno-popover__arrow` | Pointer back to the anchor (top-end corner). |
27
+ | `.juno-popover-anchor` | Optional inline wrapper to group trigger + panel (layout only). |
28
+
29
+ ## Anatomy (any platform)
30
+
31
+ - Top-layer surface `s2` + `shadow.2`, surface-padding. Pinned below the invoker
32
+ (`position-area: block-end span-inline-start`) with a `space.12` gap; flips to stay
33
+ on-screen via `position-try-fallbacks`.
34
+ - Enter: rise + fade, `motion.duration.quick` / `ease.decel`, animated open↔closed with
35
+ `@starting-style` + `transition-behavior: allow-discrete`.
36
+
37
+ ## Usage
38
+
39
+ - Reach for a popover when the content is interactive; a tooltip when it's just text.
40
+ - `popovertarget` makes the `<button>` toggle the panel and exposes `aria-expanded` /
41
+ `aria-details` implicitly — no script needed. Keep `aria-haspopup` for intent.
42
+ - Default anchoring is block-end / inline-end. Override with `position-area` (or set
43
+ `inset` on the panel's `toggle` event) for other placements or older engines.
@@ -0,0 +1,54 @@
1
+ # Rail
2
+
3
+ The side navigation column of the standard app shell — brand block on top, then
4
+ icon + label items. Collapsible to icons-only. The full shell composition
5
+ (rail + topbar + content + slide-over) is in [layout.md](../layout.md#app-shell).
6
+ On phone widths the shell swaps the rail for a [dock](./dock.md) (bottom nav).
7
+
8
+ ## Web
9
+
10
+ ```html
11
+ <nav class="juno-rail" aria-label="Primary">
12
+ <div class="juno-rail__brand">JUNO</div>
13
+ <a class="juno-rail__item" href="/library" aria-current="page">
14
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-squares-four" /></svg>
15
+ <span class="juno-rail__label">Library</span>
16
+ </a>
17
+ <a class="juno-rail__item" href="/nodes">
18
+ <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-hexagon" /></svg>
19
+ <span class="juno-rail__label">Nodes</span>
20
+ </a>
21
+ </nav>
22
+ ```
23
+
24
+ | Class / prop | Effect |
25
+ | ------------------------ | ------------------------------------------------------------------------------------------------ |
26
+ | `.juno-rail` | `s1` column, 180px, 1px `border` seam on the inline-end edge. |
27
+ | `.juno-rail__brand` | Mono uppercase brand block. |
28
+ | `.juno-rail__item` | Icon + label row; hover `s2`. |
29
+ | `.juno-rail__label` | The text — hidden when collapsed. |
30
+ | `[aria-current]` | Active item: `s3` fill + 2px role edge. Attribute, not a class. |
31
+ | `.juno-rail--collapsed` | Icons-only, `space.56` wide; width transition. |
32
+ | `.juno-rail--responsive` | Self-hides below `md`; pair with a `.juno-dock` / `.juno-pillbar` carrying `.juno-hide-from-md`. |
33
+ | `--juno-rail-width` | Override the expanded width. |
34
+ | `.juno--<role>` | Active-edge color (default `active`). |
35
+
36
+ ## Anatomy (any platform)
37
+
38
+ - Column on `s1`, hairline seam, items `space.8` × `space.16`, `font.size.12`
39
+ uppercase `label` → `data` on hover/active.
40
+ - Active = `s3` fill + `border.width.2` inline-start edge in the role color.
41
+ - Edges are logical: the rail mirrors under RTL.
42
+
43
+ ## Usage
44
+
45
+ - The app owns the collapse state (a class toggle) and sets `aria-current="page"`
46
+ on the active item — junoui styles the attribute so nav semantics stay honest.
47
+ - When collapsed, labels leave the accessibility tree: give each item an
48
+ `aria-label` (or `title`) so icons-only mode stays navigable.
49
+ - Pair with [tooltip](./tooltip.md) for collapsed-item labels.
50
+ - Adaptive shell: add `.juno-rail--responsive` so the rail self-hides below
51
+ `md` and a paired [dock](./dock.md)/[pillbar](./pillbar.md) takes over — no
52
+ need to hang `.juno-hide-below-md` on the rail yourself.
53
+ - Show a section is loading by wrapping an item's icon in an
54
+ [icon-loader](./icon-loader.md) ring.
@@ -0,0 +1,39 @@
1
+ # Data readout
2
+
3
+ A tile showing one live value: small label, large mono value, unit.
4
+
5
+ ## Web
6
+
7
+ ```html
8
+ <div class="juno-readout juno--nominal">
9
+ <span class="juno-readout__label">N1</span>
10
+ <span class="juno-readout__value">89.3</span>
11
+ <span class="juno-readout__unit">%</span>
12
+ </div>
13
+
14
+ <div class="juno-readout juno-readout--alert juno--warning">
15
+ <span class="juno-readout__label">OIL PSI</span>
16
+ <span class="juno-readout__value">12</span>
17
+ <span class="juno-readout__unit juno-text-warning">↓ LOW</span>
18
+ </div>
19
+ ```
20
+
21
+ | Class | Effect |
22
+ | -------------------------------- | ---------------------------------------------------------- |
23
+ | `.juno-readout` | `s2` tile, radius `radius.5`. Value default role = `data`. |
24
+ | `.juno--<role>` | Colors the value (status). |
25
+ | `.juno-readout--alert` | Adds a 1px role border for critical tiles. |
26
+ | `__label` / `__value` / `__unit` | Parts. |
27
+
28
+ ## Anatomy (any platform)
29
+
30
+ - Tile `s2`, padding `space.20`/`space.16`, radius `radius.5`.
31
+ - Label: `font.size.10`, uppercase, tracking `wider`, color `label`.
32
+ - Value: **B612 Mono 700**, `font.size.38`, `tabular-nums`, color = role (default `data`).
33
+ - Unit: `font.size.11`, color `label`.
34
+
35
+ ## Usage
36
+
37
+ - Value stays `data` (neutral) when nominal; switch to a status role only when the
38
+ reading itself is non-normal.
39
+ - Always B612 Mono with tabular figures so digits don't shift as values update.
@@ -0,0 +1,41 @@
1
+ # Reload
2
+
3
+ The non-blocking counterpart to the [skeleton](./skeleton.md). A skeleton
4
+ stands in for content that isn't there yet (first paint); the reload indicator
5
+ signals a refresh happening _over_ content that's already on screen — the stale
6
+ data stays readable and interactive while fresh data lands.
7
+
8
+ It's a small pulsing dot with a soft halo, centered as a fixed overlay that
9
+ eats no pointer events, so the page underneath stays fully usable.
10
+
11
+ ## Web
12
+
13
+ ```html
14
+ <div class="juno-reload" role="status" aria-label="Reloading">
15
+ <span class="juno-reload__dot"></span>
16
+ </div>
17
+ ```
18
+
19
+ | Class / prop | Effect |
20
+ | ------------------- | ------------------------------------------------------------------- |
21
+ | `.juno-reload` | Fixed, centered, `pointer-events: none` overlay. |
22
+ | `.juno-reload__dot` | `space.16` role-colored dot with a soft halo, gentle opacity pulse. |
23
+ | `.juno--<role>` | Dot color (default `active`). |
24
+
25
+ ## Usage
26
+
27
+ - Render it while a **background refetch is in flight over existing content**;
28
+ drop it when the request settles. For first-paint (no content yet) use the
29
+ [skeleton](./skeleton.md) instead.
30
+ - The app owns the state (zero JS in the component) — mount/unmount it, or
31
+ toggle a `hidden` attribute.
32
+ - The pulse uses the shared `juno-pulse` keyframe — a soft dim, not a full
33
+ blink. `prefers-reduced-motion` stops the pulse (via the base layer); the
34
+ dot stays visible so the "refreshing" state is still conveyed.
35
+
36
+ ## Accessibility
37
+
38
+ - `role="status"` + `aria-label` announces the refresh **politely**, without
39
+ stealing focus — it's a status, not an alert. Don't use `role="alert"`.
40
+ - Keep it non-blocking: `pointer-events: none` ensures it never traps clicks on
41
+ the content it floats over.
@@ -0,0 +1,46 @@
1
+ # Segmented control
2
+
3
+ An exclusive-choice pill row — filters (`ALL / PHOTOS / VIDEO`), mode switchers,
4
+ sort pickers. Native radios carry the state; zero JS.
5
+
6
+ ## Web
7
+
8
+ ```html
9
+ <fieldset class="juno-seg juno--active">
10
+ <label class="juno-seg__opt"><input type="radio" name="kind" checked /><span>ALL</span></label>
11
+ <label class="juno-seg__opt"><input type="radio" name="kind" /><span>PHOTOS</span></label>
12
+ <label class="juno-seg__opt"><input type="radio" name="kind" /><span>VIDEO</span></label>
13
+ </fieldset>
14
+ ```
15
+
16
+ JS-driven apps may use buttons instead — the app flips `aria-pressed`:
17
+
18
+ ```html
19
+ <div class="juno-seg" role="group" aria-label="Kind">
20
+ <button class="juno-seg__opt" aria-pressed="true">ALL</button>
21
+ <button class="juno-seg__opt" aria-pressed="false">PHOTOS</button>
22
+ </div>
23
+ ```
24
+
25
+ | Class | Effect |
26
+ | ---------------- | ----------------------------------------------------------------------- |
27
+ | `.juno-seg` | The row (a `<fieldset>` or `role="group"`); wraps, `space.4` gap. |
28
+ | `.juno-seg__opt` | One pill: label + hidden radio + `<span>`, or an `aria-pressed` button. |
29
+ | `.juno-seg--sm` | Dense-toolbar size (`font.size.10`), matches `.juno-btn--sm`. |
30
+ | `.juno--<role>` | Color of the checked pill (default `data`-neutral). |
31
+
32
+ ## Anatomy (any platform)
33
+
34
+ - Pill: B612, 700 weight, `font.size.11`, uppercase, tracking `font.tracking.label`,
35
+ `space.4` × `space.10` padding, `radius.3`, 1px `border` outline.
36
+ - Checked: background `s3`, border + text take the role color.
37
+ - Hover (unchecked): background `s2`. Focus ring on the pill via `:focus-visible`.
38
+
39
+ ## Usage
40
+
41
+ - Exclusive choice among 2–5 short options that should all stay visible; more than
42
+ that, use a [select](./select.md).
43
+ - The radio flavor is a working form control with arrow-key group navigation for
44
+ free — prefer it; reach for `aria-pressed` buttons only when state lives in JS.
45
+ - Wrap the radio flavor in a `<fieldset>` with a `<legend>` (may be visually hidden)
46
+ so the group is announced.
@@ -0,0 +1,33 @@
1
+ # Select
2
+
3
+ A native `<select>` styled as an input, with a token-colored chevron.
4
+
5
+ ## Web
6
+
7
+ ```html
8
+ <div class="juno-select">
9
+ <select class="juno-input">
10
+ <option>NOMINAL</option>
11
+ <option>CAUTION</option>
12
+ <option>WARNING</option>
13
+ </select>
14
+ </div>
15
+ ```
16
+
17
+ | Class | Effect |
18
+ | ---------------------------- | --------------------------------------------------------------------------------------------- |
19
+ | `.juno-select` | Wrapper; draws the chevron and positions it (inline-end). |
20
+ | `.juno-select > .juno-input` | The native `<select>` reuses the input box; `appearance:none`, room reserved for the chevron. |
21
+
22
+ ## Anatomy (any platform)
23
+
24
+ - Box is identical to [input](./input.md): `s0` bg, neutral border, radius `4`,
25
+ density-aware padding.
26
+ - Chevron: a CSS triangle in `label` color at `space.12` from the inline-end edge —
27
+ inherits a token, no SVG, mirrors under RTL.
28
+
29
+ ## Usage
30
+
31
+ - Always a real `<select>` — keyboard and screen-reader behavior come for free.
32
+ - Role classes (`.juno--<role>`) and `aria-invalid` tint the border exactly as on input.
33
+ - Wrap in [`.juno-field`](./field.md) for a label.
@@ -0,0 +1,45 @@
1
+ # Skeleton
2
+
3
+ A shimmering placeholder that stands in for content not yet loaded — same shimmer as
4
+ the [table](./table.md) row skeleton, generalized to text lines, blocks, circles, and
5
+ aspect-ratio tiles. Zero JS; honors `prefers-reduced-motion` (shimmer → static) via the
6
+ global motion contract.
7
+
8
+ ## Web
9
+
10
+ ```html
11
+ <div aria-busy="true" aria-live="polite">
12
+ <span class="juno-skeleton juno-skeleton--circle" style="--juno-skeleton-h: 48px"></span>
13
+ <span class="juno-skeleton juno-skeleton--text" style="inline-size: 60%"></span>
14
+ <span class="juno-skeleton juno-skeleton--text"></span>
15
+ <span class="juno-skeleton juno-skeleton--block"></span>
16
+ <span class="juno-skeleton juno-skeleton--tile"></span>
17
+ </div>
18
+ ```
19
+
20
+ | Class | Effect |
21
+ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
22
+ | `.juno-skeleton` | Shimmering bar; height `--juno-skeleton-h` (default `space.16`). |
23
+ | `.juno-skeleton--text` | One text line (`1em` tall, soft radius). |
24
+ | `.juno-skeleton--circle` | Round avatar / icon placeholder (square via the height). |
25
+ | `.juno-skeleton--block` | Larger surface (card / media), `space.56` tall. |
26
+ | `.juno-skeleton--tile` | Content-box mode: sized by `--juno-skeleton-ratio` instead of a fixed height — the media-grid case (a tile matching a real image/card's aspect ratio). |
27
+ | `--juno-skeleton-h` | Override the height (any length). |
28
+ | `--juno-skeleton-ratio` | `.juno-skeleton--tile` only — the aspect ratio (default `1`, e.g. `16 / 9`). |
29
+
30
+ ## Usage
31
+
32
+ - Wrap the loading region in `aria-busy="true"` (and `aria-live="polite"`) so assistive
33
+ tech announces the pending state; swap in the real content when it arrives.
34
+ - Mirror the **shape** of the content it replaces — line widths, an avatar circle, a
35
+ tile's aspect ratio — so the layout doesn't jump on load.
36
+ - Width is layout-driven: set `inline-size` (e.g. `60%`) per line; height via the variant,
37
+ `--juno-skeleton-h`, or (for `--tile`) `--juno-skeleton-ratio`.
38
+
39
+ ## Anatomy (any platform)
40
+
41
+ - Solid `s2` fill; the shimmer is a separate overlay layer sliding across on `transform`
42
+ only (never `background-position`), so it animates on the compositor instead of
43
+ repainting the gradient every frame — cheap even with hundreds of skeletons on screen.
44
+ Highlight band `s3` at 70% opacity, 1.2s ease-in-out loop. Radius `3` (block/tile `4`,
45
+ text `2`). Static fill under reduced motion.
@@ -0,0 +1,48 @@
1
+ # Slider
2
+
3
+ A track slider: a recessed channel with detent notches, an accent fill, and
4
+ a gripped lever. **Value-driven** — junoui ships the look; the app sets the value % and
5
+ owns drag / keyboard / ARIA (the same split as the overlays). Two sizes: standard (lever
6
+ always full) and `--compact` (lever recedes to a slim marker until hover / focus / drag).
7
+
8
+ ## Web
9
+
10
+ ```html
11
+ <div
12
+ class="juno-slider"
13
+ style="--juno-slider-pct: 72"
14
+ role="slider"
15
+ tabindex="0"
16
+ aria-valuenow="72"
17
+ aria-valuemin="0"
18
+ aria-valuemax="100"
19
+ >
20
+ <span class="juno-slider__channel"></span>
21
+ <span class="juno-slider__lever"></span>
22
+ </div>
23
+
24
+ <!-- compact, managed accent -->
25
+ <div class="juno-slider juno-slider--compact juno--target" style="--juno-slider-pct: 50">…</div>
26
+ ```
27
+
28
+ | Property / class | Effect |
29
+ | ------------------------ | --------------------------------------------------------------- |
30
+ | `--juno-slider-pct` | 0–100; the app sets it. Drives the fill width + lever position. |
31
+ | `.juno-slider__channel` | `s0` channel, `control-edge` border, inset shadow, 10% detents. |
32
+ | `.juno-slider__lever` | Gripped lever (`space.26`×`space.40`) at the value. |
33
+ | `.juno-slider--compact` | Lever shrinks to a marker until hover / focus / drag. |
34
+ | `.juno--<role>` | Tints the fill (default `active`). |
35
+ | `[aria-disabled="true"]` | Dimmed, not-allowed. |
36
+
37
+ ## Anatomy (any platform)
38
+
39
+ - Channel `space.20`, inset; faint detents every 10%; accent fill (8%→34%) to the value.
40
+ - Lever 26×40 (standard), gradient `s3`→`s2`, three grips; compact shrinks to 10×22.
41
+ - Lever/size transitions use `motion.duration.base` / `ease`.
42
+
43
+ ## Usage
44
+
45
+ - The app owns the value: set `--juno-slider-pct` and keep `aria-valuenow` in sync; wire
46
+ pointer-drag and arrow/Page keys, plus `aria-valuetext` when the number needs units.
47
+ (`showcase/app.js` has a reference driver.)
48
+ - Pair with a mono readout (`.juno-value`) of the current value.
@@ -0,0 +1,51 @@
1
+ # Spark
2
+
3
+ A sparkline **contract**, not a chart library. junoui deliberately ships no
4
+ charting: your app computes the `<polyline>` points; `.juno-spark` supplies the
5
+ size / stroke / role-color so every sparkline in a product matches.
6
+
7
+ ## Web
8
+
9
+ ```html
10
+ <svg
11
+ class="juno-spark juno--nominal"
12
+ viewBox="0 0 120 24"
13
+ preserveAspectRatio="none"
14
+ aria-hidden="true"
15
+ >
16
+ <polyline points="0,20 8,14 16,17 24,10 32,12 40,6 48,9" />
17
+ </svg>
18
+ ```
19
+
20
+ | Class / part | Effect |
21
+ | --------------- | ------------------------------------------------- |
22
+ | `.juno-spark` | Block, full width, `space.24` tall. |
23
+ | `polyline` | No fill, role stroke, 1.5 wide, round joins/caps. |
24
+ | `.juno--<role>` | Line color (default `active`). |
25
+
26
+ ## Generating points
27
+
28
+ 16 samples into a `0 0 120 24` viewBox, y inverted (SVG y grows downward),
29
+ with 2px headroom so the round cap never clips:
30
+
31
+ ```js
32
+ const W = 120,
33
+ H = 24,
34
+ PAD = 2;
35
+ const points = (samples) => {
36
+ const max = Math.max(...samples, 1);
37
+ const step = W / (samples.length - 1);
38
+ return samples
39
+ .map((v, i) => `${(i * step).toFixed(1)},${(H - PAD - (v / max) * (H - 2 * PAD)).toFixed(1)}`)
40
+ .join(' ');
41
+ };
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ - Trend-at-a-glance in cards and table rows; pair with a [readout](./readout.md)
47
+ or [gauge](./gauge.md) for the current value.
48
+ - A sparkline has no axes and is decorative — `aria-hidden="true"`, and put the
49
+ actual number in text next to it.
50
+ - Needs axes, tooltips, more than one series? That's a chart — use a charting
51
+ library in the app, not this.
@@ -0,0 +1,30 @@
1
+ # Status dot
2
+
3
+ A colored dot plus a text label — the most compact status signal.
4
+
5
+ ## Web
6
+
7
+ ```html
8
+ <span class="juno-status juno--nominal"><span class="juno-status__dot"></span>NOMINAL</span>
9
+ <span class="juno-status juno--active juno-status--live"
10
+ ><span class="juno-status__dot"></span>ACTIVE</span
11
+ >
12
+ ```
13
+
14
+ | Class | Effect |
15
+ | -------------------- | --------------------------------------------------------------------- |
16
+ | `.juno-status` | Inline dot + label, both in the role color. Default role = `nominal`. |
17
+ | `.juno-status__dot` | The dot (`size.dot.sm` = 8px). |
18
+ | `.juno-status--lg` | Larger dot (`size.dot.md` = 10px) for legends. |
19
+ | `.juno-status--live` | Blink animation (respects `prefers-reduced-motion`). |
20
+
21
+ ## Anatomy (any platform)
22
+
23
+ - Dot: circle, `size.dot.sm` (8px), fill = role; legend variant `size.dot.md` (10px).
24
+ - Label: `font.size.12`, tracking `font.tracking.label`, color = role.
25
+ - Gap dot↔label: `space.8`.
26
+
27
+ ## Usage
28
+
29
+ - Always show the text label; the dot color reinforces it, never replaces it.
30
+ - `--live` pulse only for genuinely live/armed signals — not decoration.