@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.
- package/CHANGELOG.md +32 -0
- package/dist/css/juno.css +162 -3
- package/docs/accessibility.md +167 -0
- package/docs/components/README.md +68 -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 +56 -0
- package/docs/components/drawer.md +60 -0
- package/docs/components/field.md +44 -0
- package/docs/components/gauge.md +48 -0
- package/docs/components/icon-loader.md +55 -0
- package/docs/components/icon.md +49 -0
- package/docs/components/input.md +45 -0
- package/docs/components/list.md +76 -0
- package/docs/components/loader.md +80 -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 +64 -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/segmented.md +46 -0
- package/docs/components/select.md +33 -0
- package/docs/components/skeleton.md +38 -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 +61 -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 +147 -0
- package/docs/flutter.md +42 -0
- package/docs/getting-started.md +70 -0
- package/docs/integration.md +95 -0
- package/docs/layout.md +201 -0
- package/docs/native.md +52 -0
- package/docs/roadmap.md +106 -0
- package/docs/tokens-reference.md +223 -0
- package/docs/web.md +89 -0
- package/package.json +3 -2
- package/src/css/base.css +7 -0
- package/src/css/components/dock.css +13 -0
- package/src/css/components/icon-loader.css +53 -0
- package/src/css/components/pillbar.css +15 -0
- package/src/css/components/rail.css +8 -0
- package/src/css/layout.css +65 -3
package/docs/roadmap.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Roadmap β missing capabilities
|
|
2
|
+
|
|
3
|
+
Gap analysis vs mature design systems (Material, Carbon, Radix, Polaris, Tailwind).
|
|
4
|
+
What junoui does **not** yet have, prioritised. The framework line holds throughout:
|
|
5
|
+
junoui ships the **look + the accessibility/structure contract**; stateful behavior
|
|
6
|
+
(focus traps, open/close, positioning, data) lives in apps or a sibling
|
|
7
|
+
`junoui-<framework>` package.
|
|
8
|
+
|
|
9
|
+
Priority key: π΄ must Β· π‘ high Β· π’ nice.
|
|
10
|
+
|
|
11
|
+
## Have today
|
|
12
|
+
|
|
13
|
+
Tokens (color Β· spacing Β· type Β· radius Β· border Β· size Β· breakpoints Β· motion Β·
|
|
14
|
+
z-index Β· elevation Β· opacity), density modes, themes (3 palettes Γ dark/light), CSS
|
|
15
|
+
components (badge, button, card, readout, status dot, loaders, **form controls** β
|
|
16
|
+
field Β· input Β· select Β· checkbox Β· radio Β· switch Β· slider; **overlays** β modal Β·
|
|
17
|
+
drawer Β· tooltip Β· popover Β· menu), layout primitives + container queries,
|
|
18
|
+
multi-platform outputs, docs, lint + tests + CI.
|
|
19
|
+
|
|
20
|
+
## Foundations (token gaps) β β
shipped 2026-06
|
|
21
|
+
|
|
22
|
+
All foundation token gaps are now built. Values: [tokens-reference.md](./tokens-reference.md);
|
|
23
|
+
usage: [design-guidelines.md](./design-guidelines.md).
|
|
24
|
+
|
|
25
|
+
| Done | What shipped |
|
|
26
|
+
| --------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
27
|
+
| **Motion** (duration + easing) | `--juno-motion-duration-{instant,quick,base,deliberate}` + `--juno-motion-ease-{decel,accel,standard,spring}`. |
|
|
28
|
+
| **Z-index scale** | `--juno-z-*` layer stack: surface Β· raised Β· anchored Β· overlay Β· alert. |
|
|
29
|
+
| **Elevation / shadow** | `--juno-shadow-{1,2,3}` (e1βe3) β border-first depth; shadow for lifted surfaces. |
|
|
30
|
+
| **Opacity scale** | `--juno-opacity-{disabled,muted,scrim}`. |
|
|
31
|
+
| **Density modes** (compact/comfy) | `data-juno-density` swaps semantic padding aliases (`--juno-pad-*`, `--juno-gap-control`). |
|
|
32
|
+
|
|
33
|
+
## Components
|
|
34
|
+
|
|
35
|
+
| Missing | Priority |
|
|
36
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
|
|
37
|
+
| ~~**Form controls** β input, textarea, select, checkbox, radio, switch, slider~~ | β
shipped |
|
|
38
|
+
| ~~**Field** wrapper β label + control + help + error + required~~ | β
shipped |
|
|
39
|
+
| ~~**Overlays** β modal/dialog, drawer, tooltip, popover, menu/dropdown~~ | β
shipped |
|
|
40
|
+
| ~~**Table / data grid** styling~~ | β
shipped |
|
|
41
|
+
| ~~**Alert / toast / inline notification**~~ | β
shipped |
|
|
42
|
+
| ~~**Tabs**, accordion / disclosure~~ | β
shipped |
|
|
43
|
+
| ~~**Skeleton** loading placeholder (pairs with loaders)~~ | β
shipped |
|
|
44
|
+
| ~~Avatar, chip/tag, divider, breadcrumb, pagination, stepper~~ | β
shipped |
|
|
45
|
+
| **Chat layer** (`junoui-chat`) β message bubble, list, day separator, system pill, attachment cards, conversation header, chat-row, rail. Requested by the buzz integration; highest-leverage gap for chat apps. | π‘ |
|
|
46
|
+
| Calendar **visuals** (event chip, day-cell) | π’ |
|
|
47
|
+
|
|
48
|
+
## Field-driven β β
shipped 2026-07
|
|
49
|
+
|
|
50
|
+
Gaps surfaced by the first production consumer (a media-server web client, 2026-07):
|
|
51
|
+
everything a real app had to hand-roll on top of junoui, rebuilt here as generic,
|
|
52
|
+
reusable pieces. The rule stands β features land app-agnostic; only the origin is
|
|
53
|
+
logged.
|
|
54
|
+
|
|
55
|
+
| Done | What shipped |
|
|
56
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
57
|
+
| **Dense sizes** | `.juno-btn--sm` + `.juno-switch--sm` for 40β46px toolbar chrome / per-row toggles. |
|
|
58
|
+
| **Segmented control** | `.juno-seg` β radio-driven exclusive pill row, zero JS; `aria-pressed` button flavor. |
|
|
59
|
+
| **Gauge** | `.juno-gauge` β determinate metric ring (conic-gradient), value centered, threshold recipe doc. |
|
|
60
|
+
| **Spark** | `.juno-spark` β sparkline size/stroke/color contract (app supplies points; no charting shipped). |
|
|
61
|
+
| **Micro badge** | `.juno-badge--micro` β mono data-UI atom for provenance/kind tags + threshold values. |
|
|
62
|
+
| **Rail + app shell** | `.juno-rail` (collapsible, `aria-current`-styled) + `layout.md#app-shell` recipe. |
|
|
63
|
+
| **Content density** | `--juno-tile-min`/`--juno-gap-content` archetype + `.juno-grid-auto--tiles`. |
|
|
64
|
+
| **Slide-over recipe** | Drawer doc: scrim + header/body/pinned-footer composition from existing parts. |
|
|
65
|
+
| **Media/system icons** | 14 Phosphor glyphs: squares-four Β· images Β· hexagon Β· puzzle-piece Β· play Β· film-strip Β· cloud Β· cloud-arrow-down Β· arrows-clockwise Β· cpu Β· hard-drives Β· sliders Β· arrows-out Β· upload-simple. |
|
|
66
|
+
|
|
67
|
+
## Mobile β β
shipped 2026-07
|
|
68
|
+
|
|
69
|
+
Mobile-first structures for narrow viewports and touch, automatic where safe,
|
|
70
|
+
opt-in where markup is needed:
|
|
71
|
+
|
|
72
|
+
| Done | What shipped |
|
|
73
|
+
| -------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
74
|
+
| **Dock** | `.juno-dock` β bottom nav, phone counterpart of the rail; sticky, safe-area padded; shell swap recipe in `layout.md`. |
|
|
75
|
+
| **Bottom sheet** | Modal auto-converts below `bp.sm`: full-width, top-rounded, slides up, stretched footer actions. |
|
|
76
|
+
| **Drawer phone fit** | Side drawers cap at `85vw` (scrim stays tappable); bottom drawer pads for the home indicator. |
|
|
77
|
+
| **Toast snackbar** | Full-width bottom stack on phones; slides up instead of sideways. |
|
|
78
|
+
| **Scrollable tabs** | Tab strip scrolls sideways instead of overflowing. |
|
|
79
|
+
| **Stacked table** | `.juno-table--stack` + `data-label` β card rows below a 480px container; real `<table>` semantics kept. |
|
|
80
|
+
| **Touch ergonomics** | `pointer: coarse` raises `--juno-size-tap-min` β 44px; `hover: none` keeps table row actions visible. |
|
|
81
|
+
| **Pillbar** | `.juno-pillbar` β floating pill bar (iOS-style): 2β5 icon destinations/actions, translucent + blurred, safe-area. |
|
|
82
|
+
| **Navbar** | `.juno-navbar` β stack top bar: back control always on the start edge, centered truncating title, trailing actions. |
|
|
83
|
+
| **List** | `.juno-list` β grouped rows (settings pattern): icon + label/support + trailing value/control/chevron. |
|
|
84
|
+
| **Tab + stack** | Shell recipe in `layout.md`: dock/pillbar switches sections, navbar backs out of pushed views. |
|
|
85
|
+
|
|
86
|
+
## Quality / infra
|
|
87
|
+
|
|
88
|
+
| Missing | Why | Priority |
|
|
89
|
+
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------ | -------- |
|
|
90
|
+
| ~~**Visual regression** snapshots (Playwright)~~ | β
shipped β `npm run test:visual` diffs every showcase page (dark + light). | β
|
|
|
91
|
+
| ~~**Changesets**~~ | β
shipped β `npm run changeset`; manual Version PR (`npm run version`) β CI publishes on merge. | β
|
|
|
92
|
+
| ~~**Icon convention / set**~~ | β
shipped β `.juno-icon` + SVG sprite (Phosphor bold, MIT). | β
|
|
|
93
|
+
| **eslint** (flat config) | JS surface is small; prettier + tests cover most. | π’ |
|
|
94
|
+
|
|
95
|
+
Done already: stylelint + prettier, `node:test` integrity suite, a11y doc + ARIA
|
|
96
|
+
contract, forced-colors, RTL via logical properties, CI gate.
|
|
97
|
+
|
|
98
|
+
## Recommended order
|
|
99
|
+
|
|
100
|
+
1. ~~**Foundation tokens** β motion, z-index, elevation, opacity, density.~~ β
done.
|
|
101
|
+
2. ~~**Form controls + field** β the single biggest capability gap.~~ β
done.
|
|
102
|
+
3. ~~**Overlays** β modal, tooltip, menu.~~ β
done.
|
|
103
|
+
4. ~~**Table / data grid** β sortable header, cell types, row states, overflow, skeleton/empty.~~ β
done.
|
|
104
|
+
5. ~~**Quality** β visual-regression snapshots + changesets.~~ β
done.
|
|
105
|
+
|
|
106
|
+
Biggest leverage: **#1 + #2.**
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
<!-- GENERATED by scripts/gen-docs.mjs β do not edit by hand. Run `npm run gen-docs`. -->
|
|
2
|
+
|
|
3
|
+
# Token reference
|
|
4
|
+
|
|
5
|
+
Every junoui token with its value and the identifier to use on each platform.
|
|
6
|
+
Colors show the authored value and the resolved sRGB hex (use the hex in any
|
|
7
|
+
tool that does not understand `oklch()`).
|
|
8
|
+
|
|
9
|
+
## How names map across platforms
|
|
10
|
+
|
|
11
|
+
| Platform | Pattern | Example (`color.standard.dark.nominal`) |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| CSS β semantic | `var(--juno-<role>)` (switches with theme attrs) | `var(--juno-nominal)` |
|
|
14
|
+
| SCSS | `$juno-color-<palette>-<mode>-<role>` | `$juno-color-standard-dark-nominal` |
|
|
15
|
+
| JS / TS | `TOKENS.<palette>.<mode>.<role>` | `TOKENS.standard.dark.nominal` |
|
|
16
|
+
| JSON (DTCG) | `color.<palette>.<mode>.<role>` | `color.standard.dark.nominal` |
|
|
17
|
+
| Android | `@color/<palette>_<mode>_<role>` | `@color/standard_dark_nominal` |
|
|
18
|
+
| iOS (Swift) | `JunoTokens.<camelCase>` | `JunoTokens.standardDarkNominal` |
|
|
19
|
+
| Flutter (Dart) | `JunoTokens.<camelCase>` | `JunoTokens.standardDarkNominal` |
|
|
20
|
+
|
|
21
|
+
Core tokens (spacing, radius, typography, β¦) use `--juno-<path-joined-by->`,
|
|
22
|
+
e.g. `space.16` β `var(--juno-space-16)`, `@dimen/space_16`, `JunoTokens.space16`.
|
|
23
|
+
|
|
24
|
+
## Color roles
|
|
25
|
+
|
|
26
|
+
Color encodes status, never decoration. Each role has exactly one meaning.
|
|
27
|
+
|
|
28
|
+
### Palette: colorblind
|
|
29
|
+
|
|
30
|
+
| Role | Dark value | Dark hex | Light value | Light hex |
|
|
31
|
+
|---|---|---|---|---|
|
|
32
|
+
| `nominal` | `#78A9FF` | `#78A9FF` | `#0043CE` | `#0043CE` |
|
|
33
|
+
| `active` | `#BE95FF` | `#BE95FF` | `#6929C4` | `#6929C4` |
|
|
34
|
+
| `target` | `#FF7EB6` | `#FF7EB6` | `#9F1853` | `#9F1853` |
|
|
35
|
+
| `caution` | `#F1C21B` | `#F1C21B` | `#7D4F00` | `#7D4F00` |
|
|
36
|
+
| `warning` | `#FF832B` | `#FF832B` | `#BA4300` | `#BA4300` |
|
|
37
|
+
| `data` | `oklch(93% 0.008 85)` | `#EAE7E2` | `oklch(14% 0.008 85)` | `#0B0906` |
|
|
38
|
+
| `data-dim` | `oklch(38% 0.010 85)` | `#45423C` | `oklch(70% 0.008 85)` | `#A19E99` |
|
|
39
|
+
| `label` | `oklch(63% 0.012 85)` | `#8C8981` | `oklch(42% 0.010 85)` | `#504D47` |
|
|
40
|
+
| `muted` | `oklch(48.5% 0.010 85)` | `#625F59` | `oklch(63% 0.008 85)` | `#8B8984` |
|
|
41
|
+
| `border` | `oklch(30% 0.012 85)` | `#312D27` | `oklch(82% 0.010 85)` | `#C7C4BD` |
|
|
42
|
+
| `border-strong` | `oklch(40% 0.012 85)` | `#4B4740` | `oklch(74% 0.010 85)` | `#AEAAA4` |
|
|
43
|
+
| `s0` | `oklch(11% 0.010 85)` | `#060402` | `oklch(97% 0.008 85)` | `#F8F5EF` |
|
|
44
|
+
| `s1` | `oklch(15% 0.012 85)` | `#0D0B06` | `oklch(93% 0.010 85)` | `#EBE7E0` |
|
|
45
|
+
| `s2` | `oklch(19% 0.012 85)` | `#16130E` | `oklch(89% 0.010 85)` | `#DEDAD3` |
|
|
46
|
+
| `s3` | `oklch(24% 0.012 85)` | `#221F19` | `oklch(85% 0.010 85)` | `#D1CDC7` |
|
|
47
|
+
|
|
48
|
+
### Palette: soft
|
|
49
|
+
|
|
50
|
+
| Role | Dark value | Dark hex | Light value | Light hex |
|
|
51
|
+
|---|---|---|---|---|
|
|
52
|
+
| `nominal` | `oklch(63% 0.13 155)` | `#38A065` | `oklch(40% 0.11 155)` | `#00582C` |
|
|
53
|
+
| `active` | `oklch(67% 0.12 210)` | `#00A8BE` | `oklch(44% 0.11 210)` | `#006174` |
|
|
54
|
+
| `target` | `oklch(58% 0.14 318)` | `#9C5CAF` | `oklch(42% 0.12 318)` | `#673477` |
|
|
55
|
+
| `caution` | `oklch(72% 0.12 75)` | `#D09945` | `oklch(50% 0.12 75)` | `#8A5700` |
|
|
56
|
+
| `warning` | `oklch(62% 0.15 30)` | `#D15D4D` | `oklch(42% 0.13 30)` | `#86281D` |
|
|
57
|
+
| `data` | `oklch(90% 0.008 88)` | `#E0DED8` | `oklch(14% 0.008 88)` | `#0A0906` |
|
|
58
|
+
| `data-dim` | `oklch(39% 0.010 88)` | `#47453F` | `oklch(69% 0.008 88)` | `#9D9B96` |
|
|
59
|
+
| `label` | `oklch(63% 0.010 88)` | `#8C8982` | `oklch(44% 0.010 88)` | `#55524C` |
|
|
60
|
+
| `muted` | `oklch(48.5% 0.010 88)` | `#615F59` | `oklch(62.5% 0.008 88)` | `#8A8782` |
|
|
61
|
+
| `border` | `oklch(31% 0.010 88)` | `#32302B` | `oklch(84% 0.010 88)` | `#CDCAC3` |
|
|
62
|
+
| `border-strong` | `oklch(41% 0.010 88)` | `#4D4A44` | `oklch(76% 0.010 88)` | `#B4B1AA` |
|
|
63
|
+
| `s0` | `oklch(12% 0.012 88)` | `#070602` | `oklch(96% 0.010 88)` | `#F4F2EA` |
|
|
64
|
+
| `s1` | `oklch(16% 0.012 88)` | `#0F0D08` | `oklch(92% 0.010 88)` | `#E7E4DD` |
|
|
65
|
+
| `s2` | `oklch(20% 0.012 88)` | `#181610` | `oklch(88% 0.010 88)` | `#DAD7D0` |
|
|
66
|
+
| `s3` | `oklch(25% 0.012 88)` | `#24211B` | `oklch(84% 0.010 88)` | `#CDCAC3` |
|
|
67
|
+
|
|
68
|
+
### Palette: standard
|
|
69
|
+
|
|
70
|
+
| Role | Dark value | Dark hex | Light value | Light hex |
|
|
71
|
+
|---|---|---|---|---|
|
|
72
|
+
| `nominal` | `oklch(73% 0.22 148)` | `#00CB4C` | `oklch(42% 0.18 148)` | `#006400` |
|
|
73
|
+
| `active` | `oklch(76% 0.16 204)` | `#00CDDF` | `oklch(47% 0.16 220)` | `#006B98` |
|
|
74
|
+
| `target` | `oklch(65% 0.26 324)` | `#D63DE6` | `oklch(45% 0.22 324)` | `#8B0097` |
|
|
75
|
+
| `caution` | `oklch(77% 0.17 73)` | `#F4A000` | `oklch(50% 0.17 73)` | `#9B4C00` |
|
|
76
|
+
| `warning` | `oklch(63% 0.22 28)` | `#F13A32` | `oklch(42% 0.21 26)` | `#A10000` |
|
|
77
|
+
| `data` | `oklch(93% 0.008 85)` | `#EAE7E2` | `oklch(14% 0.008 85)` | `#0B0906` |
|
|
78
|
+
| `data-dim` | `oklch(38% 0.010 85)` | `#45423C` | `oklch(70% 0.008 85)` | `#A19E99` |
|
|
79
|
+
| `label` | `oklch(63% 0.012 85)` | `#8C8981` | `oklch(42% 0.010 85)` | `#504D47` |
|
|
80
|
+
| `muted` | `oklch(48.5% 0.010 85)` | `#625F59` | `oklch(63% 0.008 85)` | `#8B8984` |
|
|
81
|
+
| `border` | `oklch(30% 0.012 85)` | `#312D27` | `oklch(82% 0.010 85)` | `#C7C4BD` |
|
|
82
|
+
| `border-strong` | `oklch(40% 0.012 85)` | `#4B4740` | `oklch(74% 0.010 85)` | `#AEAAA4` |
|
|
83
|
+
| `s0` | `oklch(11% 0.010 85)` | `#060402` | `oklch(97% 0.008 85)` | `#F8F5EF` |
|
|
84
|
+
| `s1` | `oklch(15% 0.012 85)` | `#0D0B06` | `oklch(93% 0.010 85)` | `#EBE7E0` |
|
|
85
|
+
| `s2` | `oklch(19% 0.012 85)` | `#16130E` | `oklch(89% 0.010 85)` | `#DEDAD3` |
|
|
86
|
+
| `s3` | `oklch(24% 0.012 85)` | `#221F19` | `oklch(85% 0.010 85)` | `#D1CDC7` |
|
|
87
|
+
|
|
88
|
+
## Core tokens
|
|
89
|
+
|
|
90
|
+
### Borders
|
|
91
|
+
|
|
92
|
+
| Token | Value | CSS variable | Flutter / iOS |
|
|
93
|
+
|---|---|---|---|
|
|
94
|
+
| `border.width.1` | `1px` | `var(--juno-border-width-1)` | `JunoTokens.borderWidth1` |
|
|
95
|
+
| `border.width.2` | `2px` | `var(--juno-border-width-2)` | `JunoTokens.borderWidth2` |
|
|
96
|
+
| `border.width.3` | `3px` | `var(--juno-border-width-3)` | `JunoTokens.borderWidth3` |
|
|
97
|
+
|
|
98
|
+
### Breakpoints
|
|
99
|
+
|
|
100
|
+
| Token | Value | CSS variable | Flutter / iOS |
|
|
101
|
+
|---|---|---|---|
|
|
102
|
+
| `bp.sm` | `640px` | `var(--juno-bp-sm)` | `JunoTokens.bpSm` |
|
|
103
|
+
| `bp.md` | `768px` | `var(--juno-bp-md)` | `JunoTokens.bpMd` |
|
|
104
|
+
| `bp.lg` | `1024px` | `var(--juno-bp-lg)` | `JunoTokens.bpLg` |
|
|
105
|
+
| `bp.xl` | `1280px` | `var(--juno-bp-xl)` | `JunoTokens.bpXl` |
|
|
106
|
+
| `bp.2xl` | `1536px` | `var(--juno-bp-2xl)` | `JunoTokens.bp2xl` |
|
|
107
|
+
|
|
108
|
+
### Elevation
|
|
109
|
+
|
|
110
|
+
| Token | Value | CSS variable | Flutter / iOS |
|
|
111
|
+
|---|---|---|---|
|
|
112
|
+
| `shadow.1` | `0 1px 2px rgb(0 0 0 / 0.30)` | `var(--juno-shadow-1)` | `JunoTokens.shadow1` |
|
|
113
|
+
| `shadow.2` | `0 4px 14px rgb(0 0 0 / 0.35)` | `var(--juno-shadow-2)` | `JunoTokens.shadow2` |
|
|
114
|
+
| `shadow.3` | `0 12px 32px rgb(0 0 0 / 0.50)` | `var(--juno-shadow-3)` | `JunoTokens.shadow3` |
|
|
115
|
+
|
|
116
|
+
### Motion
|
|
117
|
+
|
|
118
|
+
| Token | Value | CSS variable | Flutter / iOS |
|
|
119
|
+
|---|---|---|---|
|
|
120
|
+
| `motion.duration.instant` | `80ms` | `var(--juno-motion-duration-instant)` | `JunoTokens.motionDurationInstant` |
|
|
121
|
+
| `motion.duration.quick` | `140ms` | `var(--juno-motion-duration-quick)` | `JunoTokens.motionDurationQuick` |
|
|
122
|
+
| `motion.duration.base` | `200ms` | `var(--juno-motion-duration-base)` | `JunoTokens.motionDurationBase` |
|
|
123
|
+
| `motion.duration.deliberate` | `300ms` | `var(--juno-motion-duration-deliberate)` | `JunoTokens.motionDurationDeliberate` |
|
|
124
|
+
| `motion.ease.decel` | `cubic-bezier(0.2, 0.8, 0.2, 1)` | `var(--juno-motion-ease-decel)` | `JunoTokens.motionEaseDecel` |
|
|
125
|
+
| `motion.ease.accel` | `cubic-bezier(0.4, 0, 1, 1)` | `var(--juno-motion-ease-accel)` | `JunoTokens.motionEaseAccel` |
|
|
126
|
+
| `motion.ease.standard` | `cubic-bezier(0.2, 0.7, 0.3, 1)` | `var(--juno-motion-ease-standard)` | `JunoTokens.motionEaseStandard` |
|
|
127
|
+
| `motion.ease.spring` | `cubic-bezier(0.2, 0.9, 0.3, 1.2)` | `var(--juno-motion-ease-spring)` | `JunoTokens.motionEaseSpring` |
|
|
128
|
+
|
|
129
|
+
### Opacity
|
|
130
|
+
|
|
131
|
+
| Token | Value | CSS variable | Flutter / iOS |
|
|
132
|
+
|---|---|---|---|
|
|
133
|
+
| `opacity.disabled` | `0.45` | `var(--juno-opacity-disabled)` | `JunoTokens.opacityDisabled` |
|
|
134
|
+
| `opacity.muted` | `0.65` | `var(--juno-opacity-muted)` | `JunoTokens.opacityMuted` |
|
|
135
|
+
| `opacity.scrim` | `0.62` | `var(--juno-opacity-scrim)` | `JunoTokens.opacityScrim` |
|
|
136
|
+
|
|
137
|
+
### Radius
|
|
138
|
+
|
|
139
|
+
| Token | Value | CSS variable | Flutter / iOS |
|
|
140
|
+
|---|---|---|---|
|
|
141
|
+
| `radius.2` | `2px` | `var(--juno-radius-2)` | `JunoTokens.radius2` |
|
|
142
|
+
| `radius.3` | `3px` | `var(--juno-radius-3)` | `JunoTokens.radius3` |
|
|
143
|
+
| `radius.4` | `4px` | `var(--juno-radius-4)` | `JunoTokens.radius4` |
|
|
144
|
+
| `radius.5` | `5px` | `var(--juno-radius-5)` | `JunoTokens.radius5` |
|
|
145
|
+
| `radius.8` | `8px` | `var(--juno-radius-8)` | `JunoTokens.radius8` |
|
|
146
|
+
|
|
147
|
+
### Sizing
|
|
148
|
+
|
|
149
|
+
| Token | Value | CSS variable | Flutter / iOS |
|
|
150
|
+
|---|---|---|---|
|
|
151
|
+
| `size.tap.min` | `24px` | `var(--juno-size-tap-min)` | `JunoTokens.sizeTapMin` |
|
|
152
|
+
| `size.tap.comfortable` | `44px` | `var(--juno-size-tap-comfortable)` | `JunoTokens.sizeTapComfortable` |
|
|
153
|
+
| `size.dot.sm` | `8px` | `var(--juno-size-dot-sm)` | `JunoTokens.sizeDotSm` |
|
|
154
|
+
| `size.dot.md` | `10px` | `var(--juno-size-dot-md)` | `JunoTokens.sizeDotMd` |
|
|
155
|
+
|
|
156
|
+
### Spacing
|
|
157
|
+
|
|
158
|
+
| Token | Value | CSS variable | Flutter / iOS |
|
|
159
|
+
|---|---|---|---|
|
|
160
|
+
| `space.2` | `2px` | `var(--juno-space-2)` | `JunoTokens.space2` |
|
|
161
|
+
| `space.4` | `4px` | `var(--juno-space-4)` | `JunoTokens.space4` |
|
|
162
|
+
| `space.8` | `8px` | `var(--juno-space-8)` | `JunoTokens.space8` |
|
|
163
|
+
| `space.10` | `10px` | `var(--juno-space-10)` | `JunoTokens.space10` |
|
|
164
|
+
| `space.12` | `12px` | `var(--juno-space-12)` | `JunoTokens.space12` |
|
|
165
|
+
| `space.16` | `16px` | `var(--juno-space-16)` | `JunoTokens.space16` |
|
|
166
|
+
| `space.20` | `20px` | `var(--juno-space-20)` | `JunoTokens.space20` |
|
|
167
|
+
| `space.24` | `24px` | `var(--juno-space-24)` | `JunoTokens.space24` |
|
|
168
|
+
| `space.28` | `28px` | `var(--juno-space-28)` | `JunoTokens.space28` |
|
|
169
|
+
| `space.32` | `32px` | `var(--juno-space-32)` | `JunoTokens.space32` |
|
|
170
|
+
| `space.40` | `40px` | `var(--juno-space-40)` | `JunoTokens.space40` |
|
|
171
|
+
| `space.56` | `56px` | `var(--juno-space-56)` | `JunoTokens.space56` |
|
|
172
|
+
| `space.72` | `72px` | `var(--juno-space-72)` | `JunoTokens.space72` |
|
|
173
|
+
| `space.96` | `96px` | `var(--juno-space-96)` | `JunoTokens.space96` |
|
|
174
|
+
|
|
175
|
+
### Typography
|
|
176
|
+
|
|
177
|
+
| Token | Value | CSS variable | Flutter / iOS |
|
|
178
|
+
|---|---|---|---|
|
|
179
|
+
| `font.family.sans` | `'B612', sans-serif` | `var(--juno-font-family-sans)` | `JunoTokens.fontFamilySans` |
|
|
180
|
+
| `font.family.mono` | `'B612 Mono', monospace` | `var(--juno-font-family-mono)` | `JunoTokens.fontFamilyMono` |
|
|
181
|
+
| `font.weight.light` | `300` | `var(--juno-font-weight-light)` | `JunoTokens.fontWeightLight` |
|
|
182
|
+
| `font.weight.regular` | `400` | `var(--juno-font-weight-regular)` | `JunoTokens.fontWeightRegular` |
|
|
183
|
+
| `font.weight.medium` | `500` | `var(--juno-font-weight-medium)` | `JunoTokens.fontWeightMedium` |
|
|
184
|
+
| `font.weight.semibold` | `600` | `var(--juno-font-weight-semibold)` | `JunoTokens.fontWeightSemibold` |
|
|
185
|
+
| `font.weight.bold` | `700` | `var(--juno-font-weight-bold)` | `JunoTokens.fontWeightBold` |
|
|
186
|
+
| `font.size.10` | `10px` | `var(--juno-font-size-10)` | `JunoTokens.fontSize10` |
|
|
187
|
+
| `font.size.11` | `11px` | `var(--juno-font-size-11)` | `JunoTokens.fontSize11` |
|
|
188
|
+
| `font.size.12` | `12px` | `var(--juno-font-size-12)` | `JunoTokens.fontSize12` |
|
|
189
|
+
| `font.size.13` | `13px` | `var(--juno-font-size-13)` | `JunoTokens.fontSize13` |
|
|
190
|
+
| `font.size.14` | `14px` | `var(--juno-font-size-14)` | `JunoTokens.fontSize14` |
|
|
191
|
+
| `font.size.16` | `16px` | `var(--juno-font-size-16)` | `JunoTokens.fontSize16` |
|
|
192
|
+
| `font.size.18` | `18px` | `var(--juno-font-size-18)` | `JunoTokens.fontSize18` |
|
|
193
|
+
| `font.size.20` | `20px` | `var(--juno-font-size-20)` | `JunoTokens.fontSize20` |
|
|
194
|
+
| `font.size.28` | `28px` | `var(--juno-font-size-28)` | `JunoTokens.fontSize28` |
|
|
195
|
+
| `font.size.32` | `32px` | `var(--juno-font-size-32)` | `JunoTokens.fontSize32` |
|
|
196
|
+
| `font.size.36` | `36px` | `var(--juno-font-size-36)` | `JunoTokens.fontSize36` |
|
|
197
|
+
| `font.size.38` | `38px` | `var(--juno-font-size-38)` | `JunoTokens.fontSize38` |
|
|
198
|
+
| `font.size.48` | `48px` | `var(--juno-font-size-48)` | `JunoTokens.fontSize48` |
|
|
199
|
+
| `font.size.52` | `52px` | `var(--juno-font-size-52)` | `JunoTokens.fontSize52` |
|
|
200
|
+
| `font.lineHeight.none` | `1` | `var(--juno-font-lineHeight-none)` | `JunoTokens.fontLineHeightNone` |
|
|
201
|
+
| `font.lineHeight.tight` | `1.1` | `var(--juno-font-lineHeight-tight)` | `JunoTokens.fontLineHeightTight` |
|
|
202
|
+
| `font.lineHeight.snug` | `1.2` | `var(--juno-font-lineHeight-snug)` | `JunoTokens.fontLineHeightSnug` |
|
|
203
|
+
| `font.lineHeight.normal` | `1.4` | `var(--juno-font-lineHeight-normal)` | `JunoTokens.fontLineHeightNormal` |
|
|
204
|
+
| `font.lineHeight.relaxed` | `1.5` | `var(--juno-font-lineHeight-relaxed)` | `JunoTokens.fontLineHeightRelaxed` |
|
|
205
|
+
| `font.lineHeight.loose` | `1.7` | `var(--juno-font-lineHeight-loose)` | `JunoTokens.fontLineHeightLoose` |
|
|
206
|
+
| `font.tracking.tight` | `0.02em` | `var(--juno-font-tracking-tight)` | `JunoTokens.fontTrackingTight` |
|
|
207
|
+
| `font.tracking.normal` | `0.06em` | `var(--juno-font-tracking-normal)` | `JunoTokens.fontTrackingNormal` |
|
|
208
|
+
| `font.tracking.label` | `0.08em` | `var(--juno-font-tracking-label)` | `JunoTokens.fontTrackingLabel` |
|
|
209
|
+
| `font.tracking.wide` | `0.12em` | `var(--juno-font-tracking-wide)` | `JunoTokens.fontTrackingWide` |
|
|
210
|
+
| `font.tracking.caps` | `0.15em` | `var(--juno-font-tracking-caps)` | `JunoTokens.fontTrackingCaps` |
|
|
211
|
+
| `font.tracking.wider` | `0.2em` | `var(--juno-font-tracking-wider)` | `JunoTokens.fontTrackingWider` |
|
|
212
|
+
| `font.tracking.widest` | `0.3em` | `var(--juno-font-tracking-widest)` | `JunoTokens.fontTrackingWidest` |
|
|
213
|
+
|
|
214
|
+
### Z-index
|
|
215
|
+
|
|
216
|
+
| Token | Value | CSS variable | Flutter / iOS |
|
|
217
|
+
|---|---|---|---|
|
|
218
|
+
| `z.surface` | `0` | `var(--juno-z-surface)` | `JunoTokens.zSurface` |
|
|
219
|
+
| `z.raised` | `100` | `var(--juno-z-raised)` | `JunoTokens.zRaised` |
|
|
220
|
+
| `z.anchored` | `2000` | `var(--juno-z-anchored)` | `JunoTokens.zAnchored` |
|
|
221
|
+
| `z.overlay` | `4000` | `var(--juno-z-overlay)` | `JunoTokens.zOverlay` |
|
|
222
|
+
| `z.alert` | `5000` | `var(--juno-z-alert)` | `JunoTokens.zAlert` |
|
|
223
|
+
|
package/docs/web.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
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).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junoput01/junoui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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
|
|
14
|
+
"url": "git+https://github.com/junoput/junoui.git"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"design-system",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"src/css",
|
|
55
55
|
"src/icons",
|
|
56
56
|
"src/fonts",
|
|
57
|
+
"docs",
|
|
57
58
|
"README.md",
|
|
58
59
|
"CHANGELOG.md"
|
|
59
60
|
],
|
package/src/css/base.css
CHANGED
|
@@ -89,6 +89,13 @@ code, kbd, samp, pre { font-family: var(--juno-font-family-mono); }
|
|
|
89
89
|
the contract; a phone just reads the bigger one. */
|
|
90
90
|
@media (pointer: coarse) {
|
|
91
91
|
:root { --juno-size-tap-min: var(--juno-size-tap-comfortable); }
|
|
92
|
+
|
|
93
|
+
/* iOS Safari zooms the page onto any focused text field whose font-size is
|
|
94
|
+
below 16px. Hold text-entry controls (.juno-input covers input, textarea
|
|
95
|
+
and the select's inner control) at a 16px floor on touch so focusing never
|
|
96
|
+
triggers that jump. max() keeps the floor even under a scaled-down
|
|
97
|
+
--juno-font-scale, and still grows when scaled up. */
|
|
98
|
+
.juno-input { font-size: max(16px, var(--juno-font-size-16)); }
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
@keyframes juno-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
|
|
@@ -72,3 +72,16 @@
|
|
|
72
72
|
outline: var(--juno-border-width-2) solid var(--juno-active);
|
|
73
73
|
outline-offset: calc(-1 * var(--juno-space-2));
|
|
74
74
|
}
|
|
75
|
+
|
|
76
|
+
/* Fixed β same story as the pillbar: sticky only pins while the column
|
|
77
|
+
overflows, so on a short page the dock lands mid-content. Pin to the
|
|
78
|
+
viewport foot in a page-scroll shell where content height varies. Since
|
|
79
|
+
fixed leaves the flow, reserve its height at the page foot (e.g.
|
|
80
|
+
padding-block-end) so it doesn't cover the last row. In-flow
|
|
81
|
+
.juno-app-shell (main is the scroller, dock at the body foot) needs none
|
|
82
|
+
of this β prefer it. */
|
|
83
|
+
.juno-dock--fixed {
|
|
84
|
+
position: fixed;
|
|
85
|
+
inset-inline: 0;
|
|
86
|
+
inset-block-end: 0;
|
|
87
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
2
|
+
* Component β Icon loader (nav loading ring)
|
|
3
|
+
* A destination icon ringed by the spinning arc while that section loads β
|
|
4
|
+
* the "this is loading" affordance for a rail / dock / pillbar item. The
|
|
5
|
+
* icon stays static on top; the arc sizes in em around it and eats no
|
|
6
|
+
* pointer events, so the nav item still clicks through.
|
|
7
|
+
*
|
|
8
|
+
* The app owns the state: add .juno-arc--indeterminate to spin, drop it
|
|
9
|
+
* when loaded (gate the blink to first-load, not every refetch). For a
|
|
10
|
+
* determinate ring, set --juno-progress on the .juno-arc instead.
|
|
11
|
+
* Usage:
|
|
12
|
+
* <a class="juno-rail__item" href="β¦" aria-current="page">
|
|
13
|
+
* <span class="juno-icon-loader">
|
|
14
|
+
* <svg class="juno-icon" aria-hidden="true"><use href="β¦#juno-i-squares-four" /></svg>
|
|
15
|
+
* <span class="juno-arc juno-arc--indeterminate" role="status" aria-label="Loading"></span>
|
|
16
|
+
* </span>
|
|
17
|
+
* <span class="juno-rail__label">Library</span>
|
|
18
|
+
* </a>
|
|
19
|
+
*
|
|
20
|
+
* Gotcha encoded here: the arc's rotate animation overrides `transform`, so
|
|
21
|
+
* the ring is centred with inset:0 + margin:auto β never translate(-50%,-50%),
|
|
22
|
+
* which the spin would clobber.
|
|
23
|
+
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
|
24
|
+
|
|
25
|
+
.juno-icon-loader {
|
|
26
|
+
--juno-role: var(--juno-active);
|
|
27
|
+
|
|
28
|
+
/* Single-cell grid: the icon and the ring occupy the SAME cell and are
|
|
29
|
+
both centred in it, so they're guaranteed concentric β no transform (the
|
|
30
|
+
arc's rotation would clobber a translate) and no fragile margin math. The
|
|
31
|
+
ring is the larger child, so the cell sizes to it (--juno-icon-loader-ring). */
|
|
32
|
+
display: inline-grid;
|
|
33
|
+
place-items: center;
|
|
34
|
+
line-height: 0; /* drop the inline strut so the box hugs the ring */
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.juno-icon-loader > .juno-icon,
|
|
38
|
+
.juno-icon-loader > .juno-arc {
|
|
39
|
+
grid-area: 1 / 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* icon sits above the ring */
|
|
43
|
+
.juno-icon-loader > .juno-icon {
|
|
44
|
+
z-index: 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* the ring: sized in em (relative to the icon), transparent to clicks */
|
|
48
|
+
.juno-icon-loader > .juno-arc {
|
|
49
|
+
--juno-arc-size: var(--juno-icon-loader-ring, 1.9em);
|
|
50
|
+
--juno-arc-width: 0.14em;
|
|
51
|
+
|
|
52
|
+
pointer-events: none;
|
|
53
|
+
}
|
|
@@ -102,6 +102,21 @@
|
|
|
102
102
|
flex-shrink: 0;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/* Fixed β pin to the viewport instead of flowing at the column's end.
|
|
106
|
+
Sticky only pins while the column overflows; on a short (non-scrolling)
|
|
107
|
+
page the sticky pillbar lands mid-content. Use --fixed in a page-scroll
|
|
108
|
+
shell where content height varies. Centered via inset-inline:0 + the base
|
|
109
|
+
margin-inline:auto (NOT translateX, which the app-shell model avoids
|
|
110
|
+
anyway). Leaves the viewport foot free β reserve space so it doesn't cover
|
|
111
|
+
the last row (e.g. padding-block-end on the scroll region). Prefer the
|
|
112
|
+
in-flow .juno-app-shell (main scrolls, pillbar sits at the body foot) β
|
|
113
|
+
that needs no fixed at all. */
|
|
114
|
+
.juno-pillbar--fixed {
|
|
115
|
+
position: fixed;
|
|
116
|
+
inset-inline: 0;
|
|
117
|
+
z-index: var(--juno-z-anchored);
|
|
118
|
+
}
|
|
119
|
+
|
|
105
120
|
/* reduced-transparency preference β solid surface, no blur */
|
|
106
121
|
@media (prefers-reduced-transparency: reduce) {
|
|
107
122
|
.juno-pillbar {
|
|
@@ -94,3 +94,11 @@
|
|
|
94
94
|
justify-content: center;
|
|
95
95
|
padding-inline: 0;
|
|
96
96
|
}
|
|
97
|
+
|
|
98
|
+
/* Responsive β the rail self-hides below md, so you pair it with a
|
|
99
|
+
.juno-dock (or .juno-pillbar) carrying .juno-hide-from-md and get the
|
|
100
|
+
railβdock swap without hanging a juno-hide-below-md on the rail yourself.
|
|
101
|
+
Reciprocal of the dock's md pairing; see dock.css / docs/layout.md#app-shell. */
|
|
102
|
+
@media (width <= 767.98px) {
|
|
103
|
+
.juno-rail--responsive { display: none !important; }
|
|
104
|
+
}
|
package/src/css/layout.css
CHANGED
|
@@ -92,8 +92,70 @@
|
|
|
92
92
|
}
|
|
93
93
|
.juno-reel > * { scroll-snap-align: start; flex: 0 0 auto; }
|
|
94
94
|
|
|
95
|
+
/* App shell β the standard product frame, as classes instead of the
|
|
96
|
+
copy-pasted \3c style> block every consumer used to hand-roll. A rail beside
|
|
97
|
+
a body column; the body holds an optional topbar, a scrolling main region,
|
|
98
|
+
and a dock/pillbar pinned at its foot. The MAIN region is the scroller
|
|
99
|
+
(not the page), so the dock never needs sticky/fixed to stay put and the
|
|
100
|
+
mobile address bar can't clip it. Height is 100dvh (dynamic viewport) so
|
|
101
|
+
it fills the visible area under shrinking browser chrome. Pairs with the
|
|
102
|
+
viewport helpers below to swap railβdock across md; see docs/layout.md#app-shell.
|
|
103
|
+
<div class="juno-app-shell">
|
|
104
|
+
<nav class="juno-rail juno-rail--responsive" aria-label="Primary">β¦</nav>
|
|
105
|
+
<div class="juno-app-shell__body">
|
|
106
|
+
<header class="juno-app-shell__topbar">β¦</header>
|
|
107
|
+
<main class="juno-app-shell__main">β¦</main>
|
|
108
|
+
<nav class="juno-dock juno-hide-from-md" aria-label="Primary">β¦</nav>
|
|
109
|
+
</div>
|
|
110
|
+
</div> */
|
|
111
|
+
.juno-app-shell {
|
|
112
|
+
display: flex;
|
|
113
|
+
block-size: 100dvh;
|
|
114
|
+
overflow: hidden; /* the main region scrolls, not the shell */
|
|
115
|
+
padding-inline: env(safe-area-inset-left, 0) env(safe-area-inset-right, 0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.juno-app-shell__body {
|
|
119
|
+
flex: 1;
|
|
120
|
+
min-inline-size: 0;
|
|
121
|
+
display: flex;
|
|
122
|
+
flex-direction: column;
|
|
123
|
+
min-block-size: 0; /* let __main shrink and scroll instead of the shell */
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.juno-app-shell__topbar {
|
|
127
|
+
flex-shrink: 0;
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
gap: var(--juno-space-12);
|
|
131
|
+
min-block-size: var(--juno-app-shell-topbar-size, 46px);
|
|
132
|
+
padding-inline: var(--juno-pad-surface-inline);
|
|
133
|
+
padding-block-start: env(safe-area-inset-top, 0);
|
|
134
|
+
background: var(--juno-s1);
|
|
135
|
+
border-block-end: var(--juno-border-width-1) solid var(--juno-border);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.juno-app-shell__main {
|
|
139
|
+
flex: 1;
|
|
140
|
+
min-block-size: 0;
|
|
141
|
+
overflow: auto;
|
|
142
|
+
overscroll-behavior: contain;
|
|
143
|
+
padding: var(--juno-pad-surface-block) var(--juno-pad-surface-inline);
|
|
144
|
+
}
|
|
145
|
+
|
|
95
146
|
/* Hide/show by viewport breakpoint β the rare case an intrinsic primitive
|
|
96
|
-
can't express (e.g. swap a nav for a menu button).
|
|
97
|
-
|
|
147
|
+
can't express (e.g. swap a nav for a menu button). `hide-below-X` hides
|
|
148
|
+
under breakpoint X; `hide-from-X` hides at X and up. `show-*` are the
|
|
149
|
+
readable inverse aliases (show-from-X β‘ hide-below-X, show-below-X β‘
|
|
150
|
+
hide-from-X) β same media rule, name whichever reads clearer at the site. */
|
|
151
|
+
@media (width <= 639.98px) { .juno-hide-below-sm, .juno-show-from-sm { display: none !important; } }
|
|
152
|
+
|
|
153
|
+
@media (width >= 640px) { .juno-hide-from-sm, .juno-show-below-sm { display: none !important; } }
|
|
154
|
+
|
|
155
|
+
@media (width <= 767.98px) { .juno-hide-below-md, .juno-show-from-md { display: none !important; } }
|
|
156
|
+
|
|
157
|
+
@media (width >= 768px) { .juno-hide-from-md, .juno-show-below-md { display: none !important; } }
|
|
158
|
+
|
|
159
|
+
@media (width <= 1023.98px) { .juno-hide-below-lg, .juno-show-from-lg { display: none !important; } }
|
|
98
160
|
|
|
99
|
-
@media (width >=
|
|
161
|
+
@media (width >= 1024px) { .juno-hide-from-lg, .juno-show-below-lg { display: none !important; } }
|