@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
package/docs/web.md ADDED
@@ -0,0 +1,95 @@
1
+ # Web (CSS / SCSS / JS)
2
+
3
+ ## CSS — the fast path
4
+
5
+ Import the full stylesheet (tokens + base + utilities + components):
6
+
7
+ ```js
8
+ import '@junoput01/junoui/css'; // bundler
9
+ ```
10
+
11
+ ```css
12
+ @import '@junoput01/junoui/css'; /* plain CSS */
13
+ ```
14
+
15
+ Set the theme with two attributes on `<html>`:
16
+
17
+ ```html
18
+ <html data-juno-palette="standard" data-juno-mode="dark"></html>
19
+ ```
20
+
21
+ Both are optional. **Omit `data-juno-mode` and the theme follows the OS**
22
+ (`prefers-color-scheme`, live — no JS): dark systems get dark, light systems
23
+ light, in whichever palette is set (default `standard`). The base layer also
24
+ sets `color-scheme` so scrollbars and native form chrome match. An explicit
25
+ `data-juno-mode` pins the mode and ignores the OS.
26
+
27
+ Other system preferences honored out of the box: `prefers-reduced-motion`
28
+ (animations collapse), `prefers-contrast: more` (hairlines step up to the
29
+ strong border), `prefers-reduced-transparency` (translucent surfaces go
30
+ solid), `forced-colors` (system palette). Language is the app's job — junoui
31
+ ships no strings and is RTL-safe via logical properties; set `lang`/`dir` and
32
+ the layout follows.
33
+
34
+ Switch at runtime:
35
+
36
+ ```js
37
+ document.documentElement.dataset.junoPalette = 'colorblind';
38
+ document.documentElement.dataset.junoMode = 'light';
39
+ delete document.documentElement.dataset.junoMode; // back to following the OS
40
+ ```
41
+
42
+ Use semantic variables and component classes:
43
+
44
+ ```html
45
+ <span class="juno-badge juno--warning">WARNING</span>
46
+
47
+ <div style="color: var(--juno-nominal); padding: var(--juno-space-16);">…</div>
48
+ ```
49
+
50
+ Want variables only (bring your own components)? Import `@junoput01/junoui/css/tokens`.
51
+
52
+ ## SCSS
53
+
54
+ ```scss
55
+ @use '@junoput01/junoui/scss' as juno; // $juno-color-standard-dark-nominal, $juno-space-16, …
56
+ ```
57
+
58
+ All variables are `!default`, so you can override before `@use` if you fork values.
59
+
60
+ ## JS / TS
61
+
62
+ ```ts
63
+ import { TOKENS, CORE, getTokens } from '@junoput01/junoui';
64
+
65
+ getTokens('colorblind', 'light').warning; // "#BA4300"
66
+ TOKENS.standard.dark.nominal; // "oklch(73% 0.22 148)"
67
+ CORE.space['16']; // "16px"
68
+ ```
69
+
70
+ Types ship with the package (`JunoPalette`, `JunoMode`, `JunoRole`, `JunoTheme`).
71
+
72
+ ## Semantic vs explicit variables
73
+
74
+ - **Semantic** `--juno-nominal` … resolve to the _current_ theme. Use these in components.
75
+ - **Explicit** values for a specific theme live in SCSS/JS as
76
+ `…-standard-dark-nominal`. Use only when you need a fixed theme regardless of context.
77
+
78
+ ## Fonts
79
+
80
+ junoui **never fetches fonts** (a cross-origin `@import` would break a strict CSP and
81
+ phone home). `base.css` names B612 / B612 Mono via `--juno-font-family-*` but doesn't
82
+ load them. Two options:
83
+
84
+ ```js
85
+ import '@junoput01/junoui/fonts.css'; // opt-in: self-hosted B612 woff2, no network (CSP-safe)
86
+ ```
87
+
88
+ …or bring your own B612 (e.g. `@fontsource/b612`). Without either, the UI falls back to
89
+ system sans/mono. See [integration.md](./integration.md#2-fonts-opt-in-csp-safe).
90
+
91
+ ## Boot performance
92
+
93
+ An app that wants chrome on screen at the first HTML parse — before `juno.css`
94
+ or any bundle arrives — and everything else loaded in the background follows
95
+ the boot-shell ladder: [boot-shell.md](./boot-shell.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junoput01/junoui",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "junoui — a token-driven design system. Color carries semantic meaning, never decoration: every hue has one assigned role (NOMINAL / ACTIVE / TARGET / CAUTION / WARNING). Ships multi-platform tokens (CSS, SCSS, JS/TS, JSON, Android, iOS, Flutter) plus a framework-agnostic CSS component layer.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "repository": {
13
13
  "type": "git",
14
- "url": "git@github.com-junoput:junoput/junoui.git"
14
+ "url": "git+https://github.com/junoput/junoui.git"
15
15
  },
16
16
  "keywords": [
17
17
  "design-system",
@@ -29,7 +29,8 @@
29
29
  "module": "./dist/js/tokens.js",
30
30
  "types": "./dist/js/tokens.d.ts",
31
31
  "sideEffects": [
32
- "**/*.css"
32
+ "**/*.css",
33
+ "./dist/icons/inline.js"
33
34
  ],
34
35
  "exports": {
35
36
  ".": {
@@ -39,6 +40,7 @@
39
40
  },
40
41
  "./css": "./dist/css/juno.css",
41
42
  "./css/tokens": "./dist/css/juno-tokens.css",
43
+ "./css/custom-media": "./dist/css/juno-custom-media.css",
42
44
  "./fonts.css": "./dist/css/juno-fonts.css",
43
45
  "./scss": "./dist/scss/_juno-tokens.scss",
44
46
  "./tokens.json": "./dist/json/tokens.json",
@@ -47,6 +49,7 @@
47
49
  "./ios": "./dist/ios/JunoTokens.swift",
48
50
  "./flutter": "./dist/flutter/juno_tokens.dart",
49
51
  "./icons": "./dist/icons/juno-icons.svg",
52
+ "./icons/inline": "./dist/icons/inline.js",
50
53
  "./package.json": "./package.json"
51
54
  },
52
55
  "files": [
@@ -54,6 +57,7 @@
54
57
  "src/css",
55
58
  "src/icons",
56
59
  "src/fonts",
60
+ "docs",
57
61
  "README.md",
58
62
  "CHANGELOG.md"
59
63
  ],
package/src/css/base.css CHANGED
@@ -13,7 +13,37 @@
13
13
  /* System color scheme. Without data-juno-mode the theme follows the OS
14
14
  (`prefers-color-scheme`; see juno-tokens.css) — `dark light` here lets the
15
15
  UA pick matching scrollbars/form chrome. An explicit mode pins both. */
16
- :root { color-scheme: dark light; }
16
+ :root {
17
+ color-scheme: dark light;
18
+
19
+ /* Floating-nav scroll clearance — a page/scroller that a fixed dock or
20
+ pillbar floats over reserves this much room at its foot so the last row
21
+ clears the overlay. Consumers write `padding-block-end:
22
+ var(--juno-dock-clearance)` and stay correct when the dock geometry
23
+ changes — no hand-rolled calc() per app. Web-only (they carry env()), so
24
+ they live in the CSS layer, not the cross-platform token set.
25
+ --juno-dock-clearance ≈ pill height + bottom margin + breathing room;
26
+ --juno-pillbar-clearance suits the shorter centered pill. */
27
+ --juno-dock-clearance: calc(var(--juno-space-72) + var(--juno-space-20) + env(safe-area-inset-bottom, 0px));
28
+ --juno-pillbar-clearance: calc(var(--juno-space-72) + env(safe-area-inset-bottom, 0px));
29
+
30
+ /* JS-readable motion contract. CSS-only `prefers-reduced-motion` never reaches
31
+ imperative JS (scrollTo/scrollIntoView smooth behavior, rAF-driven transforms,
32
+ an "animate or not" branch) — so the preference is also exposed as a custom
33
+ property script can read with one getComputedStyle call, no matchMedia
34
+ listener required for a one-shot check:
35
+ getComputedStyle(document.documentElement)
36
+ .getPropertyValue('--juno-motion').trim() !== 'none'
37
+ A component MAY author its durations through the scale rather than a raw
38
+ duration token — `calc(var(--juno-motion-duration-base) * var(--juno-motion-scale))`
39
+ — so the reduced-motion override below collapses them to 0 without its own
40
+ per-component media query. Web-only DOM contract, so it lives
41
+ here rather than in the cross-platform token set (see --juno-dock-clearance
42
+ above for the same reasoning). */
43
+ --juno-motion: auto;
44
+ --juno-motion-scale: 1;
45
+ }
46
+
17
47
  :root[data-juno-mode='dark'] { color-scheme: dark; }
18
48
 
19
49
  /* Control surfaces. On dark panels the hairline border (~23% L) disappears, so
@@ -89,11 +119,103 @@ code, kbd, samp, pre { font-family: var(--juno-font-family-mono); }
89
119
  the contract; a phone just reads the bigger one. */
90
120
  @media (pointer: coarse) {
91
121
  :root { --juno-size-tap-min: var(--juno-size-tap-comfortable); }
122
+
123
+ /* iOS Safari is widely observed to zoom the page onto a focused text field
124
+ whose font-size is under 16px. NOTE: no primary WebKit/Apple source states
125
+ this — it is empirical behavior, not published spec, so don't cite it as
126
+ one (see docs/ios-conformance.md and ticket 20260803-032). The mitigation
127
+ is harmless either way: hold text-entry controls (.juno-input covers input,
128
+ textarea and the select's inner control) at a 16px floor on touch. max()
129
+ keeps the floor even under a scaled-down --juno-font-scale, and still grows
130
+ when scaled up. */
131
+ .juno-input { font-size: max(16px, var(--juno-font-size-16)); }
132
+
133
+ /* Kill the UA tap-highlight square on the interactive surfaces so it never
134
+ flashes past a rounded control on tap. Consumers were adding this by hand
135
+ per component; make it a first-class touch default. See 20260802-020. */
136
+ :where(
137
+ .juno-btn,
138
+ .juno-dock__item,
139
+ .juno-pillbar__item,
140
+ .juno-tabs__tab,
141
+ .juno-list__item,
142
+ .juno-menu__item
143
+ ) {
144
+ -webkit-tap-highlight-color: transparent;
145
+ }
146
+ }
147
+
148
+ /* Tappable primitives opt out of double-tap-to-zoom. A browser that still
149
+ recognises that gesture has to WAIT after the first tap to see whether a
150
+ second one is coming, which reads as a late, mushy tap on exactly the
151
+ surfaces a phone UI is built from. `manipulation` keeps panning and
152
+ pinch-zoom (so the page stays zoomable — never `none` here, that would be an
153
+ a11y regression) and drops only the double-tap.
154
+ NOT inside the pointer:coarse block above: a hybrid device (touch laptop,
155
+ iPad with a trackpad) reports a fine primary pointer while still taking
156
+ touch input, and the property is inert on a mouse anyway.
157
+ Community convention — no primary Apple/WebKit source names it; see
158
+ docs/ios-conformance.md. Named components only, so a consumer's own elements
159
+ are untouched. See 20260803-038. */
160
+ :where(
161
+ .juno-btn,
162
+ .juno-dock__item,
163
+ .juno-pillbar__item,
164
+ .juno-pillbar__overflow,
165
+ .juno-tabs__tab,
166
+ .juno-list__item,
167
+ .juno-menu__item,
168
+ .juno-seg__option,
169
+ .juno-chip,
170
+ .juno-toggle-btn
171
+ ) {
172
+ touch-action: manipulation;
173
+ }
174
+
175
+ /* Gesture-owned surfaces — for an element whose pointer events are fully
176
+ driven by app JS (drag-pan, pinch-zoom, swipe classification: a state
177
+ machine, not a native scroller). The UA must get out of the way: no
178
+ scroll/zoom gesture recognition, no callout menu on long-press, no text
179
+ selection, no tap-highlight flash. Community convention — no primary
180
+ Apple/WebKit source names any of these four properties for iOS, see
181
+ docs/ios-conformance.md; do not attribute them to Apple. Opt-in utility
182
+ class (unlike the pointer:coarse reset above, which targets junoui's own
183
+ named components), so it isn't gated behind a pointer-type query — apply
184
+ it only to elements a gesture handler actually owns.
185
+ --juno-touch-action narrows the axis without overriding the rest of the
186
+ block; default `none` hands every axis to JS. */
187
+ .juno-gesture-surface {
188
+ touch-action: var(--juno-touch-action, none);
189
+ -webkit-touch-callout: none;
190
+ user-select: none;
191
+ -webkit-tap-highlight-color: transparent;
92
192
  }
93
193
 
194
+ /* Single-axis escape hatches: the UA keeps native scrolling on one axis
195
+ while JS reads gestures on the other (e.g. a vertical list row that also
196
+ supports horizontal swipe-to-reveal). Equivalent to
197
+ `--juno-touch-action: pan-x|pan-y` on .juno-gesture-surface, offered as
198
+ standalone classes for elements that only need the axis lock and none of
199
+ the callout/selection/tap-highlight resets. */
200
+ .juno-pan-x { touch-action: pan-x; }
201
+ .juno-pan-y { touch-action: pan-y; }
202
+
94
203
  @keyframes juno-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
95
204
 
205
+ /* Softer opacity pulse — a "working / refreshing" cadence that dims rather
206
+ than fully blinks out (used by the reload indicator and any live dot that
207
+ wants a gentler beat than juno-blink). */
208
+ @keyframes juno-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
209
+
96
210
  @media (prefers-reduced-motion: reduce) {
211
+ /* Flip the JS-readable contract declared at the top of this file. Without
212
+ this the property would always report `auto` and script could never see
213
+ the preference — the whole point of exposing it. */
214
+ :root {
215
+ --juno-motion: none;
216
+ --juno-motion-scale: 0;
217
+ }
218
+
97
219
  *, *::before, *::after {
98
220
  animation-duration: 0.01ms !important;
99
221
  animation-iteration-count: 1 !important;
@@ -118,3 +240,51 @@ code, kbd, samp, pre { font-family: var(--juno-font-family-mono); }
118
240
  .juno-readout { border: 1px solid CanvasText; }
119
241
  .juno-badge { forced-color-adjust: none; } /* keep status fill meaningful */
120
242
  }
243
+
244
+ /* ── iOS standalone letterbox unlock ─────────────────────────────────────────
245
+ iOS WebKit (observed through 26.6, iPhone 16 Pro, iOS 18.7) sizes a
246
+ Home-Screen standalone window whose DOCUMENT cannot scroll as if a
247
+ retractable browser toolbar existed: `screen.height − status bar`, pinned to
248
+ the top. The reserved strip — exactly env(safe-area-inset-top) — surfaces at
249
+ the BOTTOM of the glass, outside the window, painting black on every screen.
250
+ The same install with a scrollable document gets the whole screen.
251
+
252
+ Established by measurement, not inference (nexora 20260812-006): 201 device
253
+ readings; a four-structure A/B on one install where doc-scroll = full screen
254
+ and fixed-shell = letterboxed on every cold launch; and this exact mechanism
255
+ — the shell kept, the document left scrollable behind it by an invisible
256
+ in-flow spacer — verified full-screen on the device before landing here.
257
+ Transient scrollability is NOT enough (seven timed interventions, five
258
+ controlled runs, all negative): iOS samples the resting structure.
259
+
260
+ So: keep the document scrollable behind the app. The spacer is body::after
261
+ (a consumer that needs body::after for itself overrides this at the same
262
+ gate — none of ours do), invisible, 1px wide, and taller than the LARGE
263
+ viewport so the document always overflows whatever window iOS grants.
264
+ `overscroll-behavior: none` keeps the ghost scroller from rubber-banding;
265
+ apps put `overscroll-behavior: contain` on their real scrollers so an
266
+ inner fling never chains into it.
267
+
268
+ Gate, all three required:
269
+ display-mode: standalone only installed apps are letterboxed
270
+ pointer: coarse touch devices — keeps macOS Dock apps out
271
+ -webkit-touch-callout support iOS/iPadOS WebKit only
272
+ Selectors carry `html:root` (0,1,2) on purpose: app resets commonly declare
273
+ `body { overflow: hidden }` at (0,0,1) AFTER this sheet, and the unlock must
274
+ win the cascade without !important. */
275
+ @media (display-mode: standalone) and (pointer: coarse) {
276
+ @supports (-webkit-touch-callout: none) {
277
+ html:root,
278
+ html:root body { overflow: visible; }
279
+ html:root body { overscroll-behavior: none; }
280
+
281
+ html:root body::after {
282
+ content: '';
283
+ display: block;
284
+ width: 1px;
285
+ height: calc(100lvh + 80px);
286
+ visibility: hidden;
287
+ pointer-events: none;
288
+ }
289
+ }
290
+ }