@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.
- package/CHANGELOG.md +251 -0
- package/README.md +23 -19
- package/dist/css/juno-custom-media.css +32 -0
- package/dist/css/juno.css +1355 -40
- package/dist/icons/inline.js +22 -0
- package/docs/accessibility.md +188 -0
- package/docs/boot-shell.md +295 -0
- package/docs/components/README.md +71 -0
- package/docs/components/accordion.md +49 -0
- package/docs/components/alert.md +46 -0
- package/docs/components/avatar.md +45 -0
- package/docs/components/badge.md +35 -0
- package/docs/components/breadcrumb.md +31 -0
- package/docs/components/button.md +32 -0
- package/docs/components/card.md +41 -0
- package/docs/components/checkbox.md +41 -0
- package/docs/components/chip.md +44 -0
- package/docs/components/divider.md +32 -0
- package/docs/components/dock.md +215 -0
- package/docs/components/drawer.md +100 -0
- package/docs/components/field.md +44 -0
- package/docs/components/fold-slot.md +31 -0
- package/docs/components/gauge.md +48 -0
- package/docs/components/icon-loader.md +122 -0
- package/docs/components/icon.md +70 -0
- package/docs/components/input.md +45 -0
- package/docs/components/list.md +76 -0
- package/docs/components/load-state.md +131 -0
- package/docs/components/loader.md +88 -0
- package/docs/components/menu.md +54 -0
- package/docs/components/modal.md +52 -0
- package/docs/components/navbar.md +53 -0
- package/docs/components/pagination.md +42 -0
- package/docs/components/pillbar.md +211 -0
- package/docs/components/popover.md +43 -0
- package/docs/components/rail.md +54 -0
- package/docs/components/readout.md +39 -0
- package/docs/components/reload.md +41 -0
- package/docs/components/segmented.md +46 -0
- package/docs/components/select.md +33 -0
- package/docs/components/skeleton.md +45 -0
- package/docs/components/slider.md +48 -0
- package/docs/components/spark.md +51 -0
- package/docs/components/status.md +30 -0
- package/docs/components/stepper.md +46 -0
- package/docs/components/switch.md +38 -0
- package/docs/components/table.md +126 -0
- package/docs/components/tabs.md +61 -0
- package/docs/components/thumb.md +81 -0
- package/docs/components/toast.md +48 -0
- package/docs/components/toggle-button.md +40 -0
- package/docs/components/tooltip.md +63 -0
- package/docs/design-guidelines.md +173 -0
- package/docs/flutter.md +42 -0
- package/docs/getting-started.md +93 -0
- package/docs/integration.md +95 -0
- package/docs/ios-conformance.md +224 -0
- package/docs/layout.md +271 -0
- package/docs/native.md +52 -0
- package/docs/roadmap.md +106 -0
- package/docs/tokens-reference.md +223 -0
- package/docs/web.md +95 -0
- package/package.json +7 -3
- package/src/css/base.css +171 -1
- package/src/css/components/dock.css +335 -0
- package/src/css/components/drawer.css +40 -3
- package/src/css/components/fold-slot.css +44 -0
- package/src/css/components/icon-loader.css +67 -0
- package/src/css/components/icon.css +6 -4
- package/src/css/components/load-state.css +136 -0
- package/src/css/components/loader.css +6 -0
- package/src/css/components/menu.css +4 -0
- package/src/css/components/modal.css +24 -3
- package/src/css/components/navbar.css +5 -1
- package/src/css/components/pillbar.css +222 -7
- package/src/css/components/rail.css +8 -0
- package/src/css/components/reload.css +48 -0
- package/src/css/components/skeleton.css +41 -13
- package/src/css/components/tabs.css +5 -0
- package/src/css/components/thumb.css +63 -1
- package/src/css/components/toast.css +5 -1
- package/src/css/density.css +22 -0
- package/src/css/layout.css +95 -5
- 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.
|