@junoput01/junoui 0.2.0 → 0.3.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 (61) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/css/juno.css +162 -3
  3. package/docs/accessibility.md +167 -0
  4. package/docs/components/README.md +68 -0
  5. package/docs/components/accordion.md +49 -0
  6. package/docs/components/alert.md +46 -0
  7. package/docs/components/avatar.md +45 -0
  8. package/docs/components/badge.md +35 -0
  9. package/docs/components/breadcrumb.md +31 -0
  10. package/docs/components/button.md +32 -0
  11. package/docs/components/card.md +41 -0
  12. package/docs/components/checkbox.md +41 -0
  13. package/docs/components/chip.md +44 -0
  14. package/docs/components/divider.md +32 -0
  15. package/docs/components/dock.md +56 -0
  16. package/docs/components/drawer.md +60 -0
  17. package/docs/components/field.md +44 -0
  18. package/docs/components/gauge.md +48 -0
  19. package/docs/components/icon-loader.md +55 -0
  20. package/docs/components/icon.md +49 -0
  21. package/docs/components/input.md +45 -0
  22. package/docs/components/list.md +76 -0
  23. package/docs/components/loader.md +80 -0
  24. package/docs/components/menu.md +54 -0
  25. package/docs/components/modal.md +52 -0
  26. package/docs/components/navbar.md +53 -0
  27. package/docs/components/pagination.md +42 -0
  28. package/docs/components/pillbar.md +64 -0
  29. package/docs/components/popover.md +43 -0
  30. package/docs/components/rail.md +54 -0
  31. package/docs/components/readout.md +39 -0
  32. package/docs/components/segmented.md +46 -0
  33. package/docs/components/select.md +33 -0
  34. package/docs/components/skeleton.md +38 -0
  35. package/docs/components/slider.md +48 -0
  36. package/docs/components/spark.md +51 -0
  37. package/docs/components/status.md +30 -0
  38. package/docs/components/stepper.md +46 -0
  39. package/docs/components/switch.md +38 -0
  40. package/docs/components/table.md +126 -0
  41. package/docs/components/tabs.md +61 -0
  42. package/docs/components/thumb.md +61 -0
  43. package/docs/components/toast.md +48 -0
  44. package/docs/components/toggle-button.md +40 -0
  45. package/docs/components/tooltip.md +63 -0
  46. package/docs/design-guidelines.md +147 -0
  47. package/docs/flutter.md +42 -0
  48. package/docs/getting-started.md +70 -0
  49. package/docs/integration.md +95 -0
  50. package/docs/layout.md +201 -0
  51. package/docs/native.md +52 -0
  52. package/docs/roadmap.md +106 -0
  53. package/docs/tokens-reference.md +223 -0
  54. package/docs/web.md +89 -0
  55. package/package.json +3 -2
  56. package/src/css/base.css +7 -0
  57. package/src/css/components/dock.css +13 -0
  58. package/src/css/components/icon-loader.css +53 -0
  59. package/src/css/components/pillbar.css +15 -0
  60. package/src/css/components/rail.css +8 -0
  61. package/src/css/layout.css +65 -3
@@ -0,0 +1,38 @@
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, and circles.
5
+ Zero JS; honors `prefers-reduced-motion` (shimmer → static).
6
+
7
+ ## Web
8
+
9
+ ```html
10
+ <div aria-busy="true" aria-live="polite">
11
+ <span class="juno-skeleton juno-skeleton--circle" style="--juno-skeleton-h: 48px"></span>
12
+ <span class="juno-skeleton juno-skeleton--text" style="inline-size: 60%"></span>
13
+ <span class="juno-skeleton juno-skeleton--text"></span>
14
+ <span class="juno-skeleton juno-skeleton--block"></span>
15
+ </div>
16
+ ```
17
+
18
+ | Class | Effect |
19
+ | ------------------------ | ---------------------------------------------------------------- |
20
+ | `.juno-skeleton` | Shimmering bar; height `--juno-skeleton-h` (default `space.16`). |
21
+ | `.juno-skeleton--text` | One text line (`1em` tall, soft radius). |
22
+ | `.juno-skeleton--circle` | Round avatar / icon placeholder (square via the height). |
23
+ | `.juno-skeleton--block` | Larger surface (card / media), `space.56` tall. |
24
+ | `--juno-skeleton-h` | Override the height (any length). |
25
+
26
+ ## Usage
27
+
28
+ - Wrap the loading region in `aria-busy="true"` (and `aria-live="polite"`) so assistive
29
+ tech announces the pending state; swap in the real content when it arrives.
30
+ - Mirror the **shape** of the content it replaces — line widths, an avatar circle — so
31
+ the layout doesn't jump on load.
32
+ - Width is layout-driven: set `inline-size` (e.g. `60%`) per line; height via the variant
33
+ or `--juno-skeleton-h`.
34
+
35
+ ## Anatomy (any platform)
36
+
37
+ - `s2`→`s3`→`s2` horizontal gradient, `background-size: 200%`, animated 1.2s ease-in-out
38
+ loop. Radius `3` (block `4`, 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.
@@ -0,0 +1,46 @@
1
+ # Stepper
2
+
3
+ A sequence of steps with numbered markers and connectors that shows progress through a
4
+ multi-step flow. Each step carries `data-state` — `complete` · `current` · `upcoming`;
5
+ the active one gets `aria-current="step"`. Built on a real `<ol>`; the app sets the
6
+ states. Zero JS. Reads `--juno-role` (default `active`) for the accent.
7
+
8
+ ## Web
9
+
10
+ ```html
11
+ <ol class="juno-stepper">
12
+ <li class="juno-stepper__step" data-state="complete">
13
+ <span class="juno-stepper__marker">
14
+ <svg class="juno-icon juno-icon--sm" aria-hidden="true"><use href="…#juno-i-check" /></svg>
15
+ </span>
16
+ <span class="juno-stepper__label">Plan</span>
17
+ </li>
18
+ <li class="juno-stepper__step" data-state="current" aria-current="step">
19
+ <span class="juno-stepper__marker">2</span>
20
+ <span class="juno-stepper__label">Build</span>
21
+ </li>
22
+ <li class="juno-stepper__step" data-state="upcoming">
23
+ <span class="juno-stepper__marker">3</span>
24
+ <span class="juno-stepper__label">Ship</span>
25
+ </li>
26
+ </ol>
27
+ ```
28
+
29
+ | Part / attr | Effect |
30
+ | ------------------------- | ----------------------------------------------------------- |
31
+ | `.juno-stepper` | `<ol>` row; `--juno-stepper-marker` sizes the circle. |
32
+ | `.juno-stepper__step` | One step; set `data-state` + connector lights when reached. |
33
+ | `.juno-stepper__marker` | Numbered circle (or a `check` icon when complete). |
34
+ | `.juno-stepper__label` | Step caption. |
35
+ | `data-state="complete"` | Filled accent marker + lit connector. |
36
+ | `data-state="current"` | Ringed accent marker; pair with `aria-current="step"`. |
37
+ | `data-state="upcoming"` | Muted marker + connector. |
38
+ | `.juno-stepper--vertical` | Stack steps; connector runs down the marker column. |
39
+
40
+ ## Usage
41
+
42
+ - Set `aria-current="step"` on the current step so assistive tech announces position.
43
+ - The **state drives the color**, not vice-versa — `complete` and `current` both light
44
+ the accent + their incoming connector; `upcoming` stays muted.
45
+ - Use a `check` icon for completed markers, the number for current / upcoming.
46
+ - Change the accent with a role class on `.juno-stepper` (e.g. `.juno--nominal`).
@@ -0,0 +1,38 @@
1
+ # Switch
2
+
3
+ A labeled rocker: a squared track that lights green (`nominal`) when ON, with an
4
+ **ON / OFF legend** and a sliding knob. A real checkbox drives it; the app owns state.
5
+
6
+ ## Web
7
+
8
+ ```html
9
+ <label class="juno-switch">
10
+ <input class="juno-switch__input" type="checkbox" role="switch" checked />
11
+ <span class="juno-switch__track"></span>
12
+ <span>AUTO-SCALING</span>
13
+ </label>
14
+ ```
15
+
16
+ | Class | Effect |
17
+ | ------------------------ | ---------------------------------------------------------------------------------- |
18
+ | `.juno-switch` | Inline-flex label row; `gap-control`, `data` text. |
19
+ | `.juno-switch__input` | The checkbox — visually hidden, still focusable / tab-ordered. |
20
+ | `.juno-switch__track` | 70 × `space.28` track; OFF = `control-edge`, ON = role (`nominal`). Legend + knob. |
21
+ | `.juno-switch--sm` | Quiet 40 × `space.20` track: no printed legend, smaller knob. For per-row toggles. |
22
+ | `.juno--<role>` | On-color of the track (put on `__track`). |
23
+ | `:checked` / `:disabled` | Slides the knob to inline-end + legend ON / dims. |
24
+
25
+ ## Anatomy (any platform)
26
+
27
+ - Track 70 × `space.28`, radius `4`, inset shadow. Knob 26 × 22, radius `4`, gradient
28
+ `s3`→`s2`, three grip lines; printed ON / OFF legend (mono 10px, `muted` → role).
29
+ - Knob travels via `inset-inline-start` (mirrors under RTL), `motion.duration.quick`;
30
+ honors `prefers-reduced-motion`.
31
+
32
+ ## Usage
33
+
34
+ - Switch = immediate state change (a setting that takes effect now). For form values
35
+ submitted later, prefer a [checkbox](./checkbox.md).
36
+ - The default rocker is for settings panels; `--sm` for columns of row toggles
37
+ (plugin lists, table rows) where five rockers would shout.
38
+ - Add `role="switch"`; the app toggles `checked` + `aria-checked`.
@@ -0,0 +1,126 @@
1
+ # Table / data grid
2
+
3
+ Dense, scannable rows that read like instrument readouts — numbers in mono and
4
+ right-aligned, semantic color reserved for status, and the active-cyan rail marking the
5
+ row in focus. junoui dresses a **real `<table>`** and ships the **look + the ARIA
6
+ contract**; the app owns sort, selection, inline-edit, pagination, and filtering.
7
+
8
+ ## Web
9
+
10
+ ```html
11
+ <div class="juno-table-scroll">
12
+ <table class="juno-table juno-table--zebra juno-table--sticky">
13
+ <thead>
14
+ <tr>
15
+ <th aria-sort="ascending">SERVICE</th>
16
+ <th class="juno-table__num" aria-sort="none">CPU</th>
17
+ <th>STATUS</th>
18
+ </tr>
19
+ </thead>
20
+ <tbody>
21
+ <tr aria-selected="true">
22
+ <td class="juno-table__id">api-gateway</td>
23
+ <td class="juno-table__num">62</td>
24
+ <td><span class="juno-badge juno-badge--soft juno--nominal">HEALTHY</span></td>
25
+ </tr>
26
+ </tbody>
27
+ </table>
28
+ </div>
29
+ ```
30
+
31
+ | Class | Effect |
32
+ | -------------------------- | ----------------------------------------------------------------------- |
33
+ | `.juno-table-scroll` | Overflow-scrolling viewport (`max-block-size: 480px`) + thin scrollbar. |
34
+ | `.juno-table` | The `<table>`: header `s2`, mono numerics, `border` row rules. |
35
+ | `.juno-table--sticky` | Header holds (`position: sticky`) while the body scrolls. |
36
+ | `.juno-table--zebra` | Stripes even body rows (`s2` dark / `s1` light). |
37
+ | `.juno-table--compact` | Tighter row padding (per-table; independent of global density). |
38
+ | `.juno-table--stack` | Phone mode: rows become label/value cards below 480px (see below). |
39
+ | `th[aria-sort]` | Marks a sortable column; `ascending` / `descending` draw the arrow. |
40
+ | `tr[aria-selected="true"]` | Active rail (left) + cyan row wash. |
41
+
42
+ ### Cell flavors
43
+
44
+ | Class | Use |
45
+ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------- |
46
+ | `.juno-table__id` | Identifier — mono, bold (codes, hostnames, IDs). |
47
+ | `.juno-table__num` | Numeric — mono, bold, end-aligned (measured values). |
48
+ | `.juno-table__mono` | Secondary mono (routes, sub-codes) — quieter `label` color. |
49
+ | `.juno-table__time` | Timestamp — mono, end-aligned, muted. |
50
+ | `.juno-table__trend` | Role-colored mono delta; author supplies the `▲` / `▼` + sign. |
51
+ | `.juno-table__meter` | Inline track + role fill + value; set `--juno-table-fill` (0–100). |
52
+ | `.juno-table__actions` | Icon buttons (`__action`), revealed on row hover / focus. |
53
+ | `.juno-badge--soft` | Status as a low-fill role chip (see [badge](./badge.md)). |
54
+ | `.juno-table__check` | Selection-column cell (44px, centered). Holds a `.juno-checkbox`. |
55
+ | `.juno-table__editable` | Editable cell: dashed `__mark` underline + hover ring. App swaps `__mark` for a `.juno-table__edit-input` on double-click. |
56
+
57
+ ### Overflow (set on a cell)
58
+
59
+ | Class | Behavior |
60
+ | ----------------------- | --------------------------------------------------------------------- |
61
+ | `.juno-table__truncate` | Single line + ellipsis (default). Pair with a `title` / tooltip. |
62
+ | `.juno-table__clamp` | Two lines, then ellipsis. Wrap the text in `.juno-table__clamp-text`. |
63
+ | `.juno-table__wrap` | Full value; row grows. Reserve for one descriptive column. |
64
+
65
+ All three cap at `--juno-cell-max` (default 240px).
66
+
67
+ ### Stacked mode (phone widths)
68
+
69
+ Wide tables don't shrink — they either scroll (the default: `.juno-table-scroll`
70
+ scrolls sideways too) or **stack**. Opt in with `.juno-table--stack`, give every
71
+ `td` a `data-label` echoing its column header, and make the wrapper a container:
72
+
73
+ ```html
74
+ <div class="juno-table-scroll" style="container-type: inline-size">
75
+ <table class="juno-table juno-table--stack">
76
+ <tbody>
77
+ <tr>
78
+ <td data-label="FLIGHT" class="juno-table__id">JU-204</td>
79
+ <td data-label="ALT" class="juno-table__num">37,000</td>
80
+ </tr>
81
+ </tbody>
82
+ </table>
83
+ </div>
84
+ ```
85
+
86
+ Below a 480px container the header row hides, each row becomes a bordered card,
87
+ and each cell renders as a label/value pair (the `data-label` becomes the label).
88
+ The selection rail moves to the card's edge, and row actions stay visible (no
89
+ hover on touch). Semantics stay a real `<table>` — only the display flips.
90
+
91
+ ### Framing rows
92
+
93
+ `.juno-table__toolbar` (top), `.juno-table__foot` (bottom), and `.juno-table__bulk`
94
+ (the selection bar — role `active`, with `.juno-table__bulk-count`) are flex rows you
95
+ stack around the scroll viewport. Frame the whole thing in a [`.juno-card`](./card.md)
96
+ for the border + radius + clipped corners.
97
+
98
+ ### States
99
+
100
+ - `.juno-table__skeleton` — shimmer bar for loading rows (honors `prefers-reduced-motion`).
101
+ - `.juno-table__empty` + `.juno-table__empty-icon` — the no-data block.
102
+
103
+ ## Anatomy (any platform)
104
+
105
+ - Header: `s2`, `font.size.10` uppercase `label`, `control-edge` underline, sticky.
106
+ - Body cell: `space.12`/`space.16` padding (`space.8` block when compact), `border` rule,
107
+ `data` text. Numerics mono + bold + end-aligned.
108
+ - Selected row: `active` left rail (`border.width.3`) + `color-mix(active 11%)` wash.
109
+ - Status is a soft badge per row; caution / warning tint **only the note**, never the
110
+ whole row, so the grid stays calm.
111
+
112
+ ## Usage (the app's half)
113
+
114
+ junoui can't sort or select in CSS — wire these and keep the ARIA in sync:
115
+
116
+ - **Sort:** click a `th`; set its `aria-sort` to `ascending` / `descending` (others to
117
+ `none`) and reorder the rows.
118
+ - **Select:** a `.juno-checkbox` per row (in a `.juno-table__check` cell) toggles
119
+ `aria-selected` on the `<tr>`; reflect the count in a `.juno-table__bulk` bar. The
120
+ header "select all" checkbox drives every visible row (use `indeterminate` for a
121
+ partial set).
122
+ - **Inline edit:** swap the cell's text for an [`.juno-input`](./input.md) on
123
+ double-click; Enter commits, Esc cancels.
124
+ - **Paginate / filter:** owned by the app; `.juno-table__foot` is just the frame.
125
+
126
+ `showcase/app.js` (`initTables`) is a reference driver for sort + selection.
@@ -0,0 +1,61 @@
1
+ # Tabs
2
+
3
+ A horizontal tablist with an active-cyan underline; the selected tab shows its panel.
4
+ junoui ships the look + the ARIA contract; the app owns switching, panel visibility, and
5
+ arrow-key roving focus.
6
+
7
+ ## Web
8
+
9
+ ```html
10
+ <div class="juno-tabs">
11
+ <div class="juno-tabs__list" role="tablist" aria-label="Service detail">
12
+ <button class="juno-tabs__tab" role="tab" id="t1" aria-controls="p1" aria-selected="true">
13
+ OVERVIEW
14
+ </button>
15
+ <button class="juno-tabs__tab" role="tab" id="t2" aria-controls="p2" aria-selected="false">
16
+ METRICS
17
+ </button>
18
+ <button
19
+ class="juno-tabs__tab"
20
+ role="tab"
21
+ id="t3"
22
+ aria-controls="p3"
23
+ aria-selected="false"
24
+ disabled
25
+ >
26
+ CONFIG
27
+ </button>
28
+ </div>
29
+ <div class="juno-tabs__panel" role="tabpanel" id="p1" aria-labelledby="t1">…</div>
30
+ <div class="juno-tabs__panel" role="tabpanel" id="p2" aria-labelledby="t2" hidden>…</div>
31
+ <div class="juno-tabs__panel" role="tabpanel" id="p3" aria-labelledby="t3" hidden>…</div>
32
+ </div>
33
+ ```
34
+
35
+ | Class | Effect |
36
+ | -------------------------------- | -------------------------------------------------------------- |
37
+ | `.juno-tabs__list` | The `role="tablist"` row; bottom rule the tabs sit on. |
38
+ | `.juno-tabs__tab` | A tab button. `label` at rest → `data` on hover. |
39
+ | `.juno-tabs__tab[aria-selected]` | Selected: `active` text + `active` underline indicator. |
40
+ | `.juno-tabs__tab:disabled` | Dimmed, not selectable. |
41
+ | `.juno-tabs__panel` | The `role="tabpanel"`; `[hidden]` when its tab isn't selected. |
42
+
43
+ ## Anatomy (any platform)
44
+
45
+ - Tabs sit on a 1px `border` rule; the selected tab paints a `border.width.2` `active`
46
+ underline that overlaps the rule. Text `label` → `active` when selected.
47
+ - Panel gets `space.20` of leading space above its content.
48
+
49
+ ## Usage (the app's half)
50
+
51
+ junoui can't switch panels in CSS — wire it and keep the ARIA in sync:
52
+
53
+ - On tab activation: set its `aria-selected="true"` (others `false`), unhide its panel
54
+ (`[hidden]` on the rest), and move the roving `tabindex` (selected = `0`, rest = `-1`).
55
+ - **Keyboard:** `←` / `→` move between tabs (wrapping), `Home` / `End` jump to the ends;
56
+ activation follows focus. `showcase/app.js` (`initTabs`) is a reference driver.
57
+ - For a stable layout, give panels equal-ish height or let content flow; don't animate
58
+ height unless you measure it.
59
+ - Tabs never wrap: when the strip runs out of room (phone widths) it scrolls
60
+ sideways, so every tab stays reachable. Keep labels short anyway — a scrolled
61
+ tab is a hidden tab until swiped to.
@@ -0,0 +1,61 @@
1
+ # Thumb
2
+
3
+ A media frame that never shows the browser's broken-image glyph. A muted
4
+ placeholder (icon on `s2`) sits under the media; the image covers it while
5
+ present. **Loading** is [skeleton](./skeleton.md)'s job — thumb handles media
6
+ that is **missing or failed**.
7
+
8
+ ## Web
9
+
10
+ ```html
11
+ <!-- normal: image covers the placeholder -->
12
+ <figure class="juno-thumb" style="aspect-ratio: 1">
13
+ <img src="…" alt="Sunset clip" onerror="this.remove()" />
14
+ </figure>
15
+
16
+ <!-- known-missing: ship no <img>; the placeholder just shows -->
17
+ <figure
18
+ class="juno-thumb juno-thumb--video"
19
+ style="aspect-ratio: 16/9"
20
+ role="img"
21
+ aria-label="Preview unavailable"
22
+ >
23
+ <figcaption class="juno-thumb__label">Unavailable</figcaption>
24
+ </figure>
25
+ ```
26
+
27
+ | Class / attr | Effect |
28
+ | ------------------------- | -------------------------------------------------------------- |
29
+ | `.juno-thumb` | Frame: `s2` fill, hairline border, `radius.3`, centered glyph. |
30
+ | `.juno-thumb--video` | Play glyph instead of the image glyph. |
31
+ | `.juno-thumb__label` | Optional uppercase micro-caption under the glyph. |
32
+ | `> img` / `> video` | Covers the frame (`object-fit: cover`). |
33
+ | `onerror="this.remove()"` | The whole JS contract — optional, stateless, one attribute. |
34
+
35
+ ## Anatomy (any platform)
36
+
37
+ - Frame: surface `s2`, 1px `border`, radius `3`; size from the app
38
+ (`aspect-ratio` inline or the [tiles grid](../layout.md)).
39
+ - Glyph: 28% of the frame (capped `space.32`), `muted` color — a missing thumb
40
+ is **not** a warning state; the placeholder stays neutral.
41
+ - Media covers the full frame, center-cropped.
42
+
43
+ ## Failure contract
44
+
45
+ There is no portable CSS-only way to detect a broken image, so the contract is
46
+ explicit:
47
+
48
+ - **Failed to load** → `onerror="this.remove()"`: the broken element removes
49
+ itself, the placeholder underneath shows.
50
+ - **Known missing** (offline node, purged cache, no permission) → render no
51
+ media element at all.
52
+ - When no media renders, the alt text is gone too — put the accessible name on
53
+ the figure (`role="img"` + `aria-label`) or show a `__label`.
54
+
55
+ ## Usage
56
+
57
+ - Media walls (pairs with `.juno-grid-auto--tiles`), avatars' big cousin,
58
+ video poster slots, attachment previews.
59
+ - Keep the placeholder neutral; if failure _matters_ (broken pipeline), say it
60
+ with a [badge](./badge.md) or [alert](./alert.md) next to the thumb.
61
+ - Retry, lazy-load, LQIP/blur-up: app or `junoui-<framework>` territory.
@@ -0,0 +1,48 @@
1
+ # Toast / snackbar
2
+
3
+ A transient, floating notification. Same role-rail language as the [alert](./alert.md),
4
+ but elevated and stacked in a fixed corner on the reserved **alert layer**
5
+ (`--juno-z-alert`, above modals). junoui ships the look + enter/exit motion; the app owns
6
+ creation, auto-dismiss timing, and the live-region announcement.
7
+
8
+ ## Web
9
+
10
+ ```html
11
+ <!-- one stack per corner; the app appends toasts into it -->
12
+ <div class="juno-toast-stack" id="toasts" aria-live="polite"></div>
13
+
14
+ <!-- a toast (app-created) -->
15
+ <div class="juno-toast juno--nominal" role="status">
16
+ <span class="juno-toast__icon" aria-hidden="true">✓</span>
17
+ <span class="juno-toast__text">Build promoted.</span>
18
+ <button class="juno-toast__close" aria-label="Dismiss">✕</button>
19
+ </div>
20
+ ```
21
+
22
+ | Class | Effect |
23
+ | ------------------------------------------ | ------------------------------------------------------------------- |
24
+ | `.juno-toast-stack` | Fixed container on `z.alert`; bottom-end, stacks its toasts. |
25
+ | `.juno-toast-stack--top` / `--start` | Move the stack to the top / inline-start. |
26
+ | `.juno-toast` | `s2` surface, `shadow.2`, role start-rail. Default `active`. |
27
+ | `.juno-toast--leaving` | Exit state (app adds it, then removes the node on `transitionend`). |
28
+ | `.juno-toast__icon` / `__text` / `__close` | Glyph · message · dismiss. |
29
+ | `.juno--<role>` | Sets the rail + icon color. |
30
+
31
+ ## Anatomy (any platform)
32
+
33
+ - Stack: `position: fixed`, `z.alert` (5000), `inline-size: min(360px, …)`, `pointer-events:
34
+ none` so only the toasts catch clicks.
35
+ - Toast: solid `s2` + `shadow.2` + role rail. Enters via `@starting-style` (fade + slide),
36
+ exits on `.juno-toast--leaving`; both honor `prefers-reduced-motion`.
37
+
38
+ ## Usage
39
+
40
+ - The app creates and destroys toasts: append to the stack, start a timer (~4s), then add
41
+ `.juno-toast--leaving` and remove on `transitionend`. `showcase/app.js` (`showToast`) is a
42
+ reference driver.
43
+ - Put `aria-live="polite"` on the stack (`assertive` for errors) so new toasts announce.
44
+ - Keep messages short and self-contained; one line. For anything needing a decision, use a
45
+ [modal](./modal.md) or inline [alert](./alert.md).
46
+ - **Phone widths (< `bp.sm`) are automatic:** the stack goes full-width along the
47
+ bottom edge (thumb reach), clear of the home indicator, and toasts slide up
48
+ instead of sideways. No extra class.
@@ -0,0 +1,40 @@
1
+ # Toggle button
2
+
3
+ An Airbus-style illuminated-legend pushbutton: the body stays dark; a top strip lights
4
+ (`nominal`) and the surround glows when engaged. A real `<button aria-pressed>`; the app
5
+ flips `aria-pressed`. Use for panel-style on/off controls where the lit legend matters.
6
+
7
+ ## Web
8
+
9
+ ```html
10
+ <button class="juno-toggle-btn" aria-pressed="true">
11
+ <span class="juno-toggle-btn__strip"></span>
12
+ <span class="juno-toggle-btn__row">
13
+ <span class="juno-toggle-btn__text">
14
+ <span class="juno-toggle-btn__legend">AUTO-SCALING</span>
15
+ <span class="juno-toggle-btn__desc">Scale workers with load</span>
16
+ </span>
17
+ <span class="juno-toggle-btn__status">ON</span>
18
+ </span>
19
+ </button>
20
+ ```
21
+
22
+ | Class | Effect |
23
+ | -------------------------------------------------- | --------------------------------------------------- |
24
+ | `.juno-toggle-btn` | Pushbutton tile on `control-surface`. |
25
+ | `[aria-pressed="true"]` | Border → role, surround glow, strip + status light. |
26
+ | `.juno-toggle-btn__strip` | Top legend strip; `s3` (off) → role (on) with glow. |
27
+ | `.juno-toggle-btn__legend` / `__desc` / `__status` | Mono legend, sub-label, ON/OFF status. |
28
+ | `.juno--<role>` | Lit color (default `nominal`). |
29
+ | `:disabled` | Dimmed, not-allowed. |
30
+
31
+ ## Anatomy (any platform)
32
+
33
+ - Tile border `control-edge` → role when pressed, plus a 20% role outer glow. Strip
34
+ `space.4` tall lights to role with an 8px glow. `motion.duration.quick` transitions.
35
+
36
+ ## Usage
37
+
38
+ - A latching control whose engaged state must read at a glance (system arm/enable).
39
+ - Use a real `<button>` and keep `aria-pressed` in sync; for inline on/off in a form,
40
+ prefer the [switch](./switch.md).
@@ -0,0 +1,63 @@
1
+ # Tooltip
2
+
3
+ A transient hover/focus label. Pure CSS reveal — no JS. Four placements with an arrow.
4
+
5
+ ## Web
6
+
7
+ ```html
8
+ <span class="juno-tooltip">
9
+ <button tabindex="0">SPD</button>
10
+ <span class="juno-tooltip__bubble" role="tooltip">
11
+ Requests / sec · 248
12
+ <span class="juno-tooltip__arrow"></span>
13
+ </span>
14
+ </span>
15
+
16
+ <!-- placements -->
17
+ <span class="juno-tooltip__bubble juno-tooltip__bubble--right">…</span>
18
+ <span class="juno-tooltip__bubble juno-tooltip__bubble--bottom">…</span>
19
+ <span class="juno-tooltip__bubble juno-tooltip__bubble--left">…</span>
20
+ ```
21
+
22
+ | Class | Effect |
23
+ | ----------------------------------------------- | -------------------------------------------------------- |
24
+ | `.juno-tooltip` | Relative wrapper around the trigger. |
25
+ | `.juno-tooltip__bubble` | `s3` bubble, mono 11px, shadow `2`, `z.anchored` (2000). |
26
+ | `--top` (default) `--right` `--bottom` `--left` | Placement of the bubble + arrow. |
27
+ | `.juno-tooltip__arrow` | Rotated-square pointer back to the trigger. |
28
+
29
+ Shows on `:hover` or `:focus-within` of the wrapper; `motion.duration.quick` (140ms)
30
+ / `ease.decel` rise.
31
+
32
+ ## Top-layer mode (escaping clipping)
33
+
34
+ The pure-CSS reveal is clipped if an ancestor sets `overflow`/`clip` (cards, scroll
35
+ areas). To lift it above everything, promote the bubble to a `popover="hint"` — it then
36
+ renders in the browser **top layer**, immune to ancestor clipping. The bubble already
37
+ carries `:popover-open` + anchor-positioning styles; a tiny stateless enhancer toggles it
38
+ and points `position-anchor` at the trigger:
39
+
40
+ ```js
41
+ const name = `--juno-tip-${i}`;
42
+ trigger.style.anchorName = name;
43
+ bubble.style.positionAnchor = name;
44
+ bubble.popover = 'hint';
45
+ trigger.addEventListener('pointerenter', () => bubble.showPopover());
46
+ trigger.addEventListener('pointerleave', () => bubble.hidePopover());
47
+ trigger.addEventListener('focus', () => bubble.showPopover());
48
+ trigger.addEventListener('blur', () => bubble.hidePopover());
49
+ ```
50
+
51
+ (`showcase/app.js` ships this as `initTooltips()`.) With no enhancer the component still
52
+ works as the CSS-only hover tooltip — just clippable by its container.
53
+
54
+ ## Anatomy (any platform)
55
+
56
+ - Bubble offset `space.8` from the trigger edge; small rise on reveal.
57
+ - Content is mono (codes/values); single line (`white-space: nowrap`).
58
+
59
+ ## Usage
60
+
61
+ - Reference text only — never put focusable controls in a tooltip (use a [popover](./popover.md)).
62
+ - Give the trigger a tabstop and the bubble `role="tooltip"` + `aria-describedby` so it
63
+ reveals on keyboard focus, not just hover.