@axiapps/axi-design 1.8.0 → 1.10.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/README.md +2 -0
- package/dist/axi.css +148 -4
- package/docs/RULES.md +17 -0
- package/docs/superpowers/plans/2026-09-23-axi-docs-site.md +2376 -0
- package/docs/superpowers/specs/2026-09-23-axi-docs-site-design.md +333 -0
- package/package.json +1 -1
- package/src/base.css +2 -0
- package/src/data.css +36 -0
- package/src/primitives.css +110 -4
package/README.md
CHANGED
|
@@ -83,6 +83,8 @@ not "what does the system look like". Everything else is
|
|
|
83
83
|
| `--axi-bars-gap` | gap between columns in `.axi-bars` | `6px` | `<div class="axi-bars" style="--axi-bars-gap: 2px">` |
|
|
84
84
|
| `--axi-plot-h` | height of a `.axi-plot` or `.axi-bars` | `180px` | `<div class="axi-plot" style="--axi-plot-h: 240px">` |
|
|
85
85
|
| `--axi-plot-rows` | how many horizontal rules a `.axi-plot` draws | `4` | `<div class="axi-plot" style="--axi-plot-rows: 6">` |
|
|
86
|
+
| `--axi-tick-w` / `--axi-tick-h` | size of one `.axi-ticks__tick` | `5px` / `15px` | `<div class="axi-ticks" style="--axi-tick-w: 7px">` |
|
|
87
|
+
| `--axi-ticks-gap` | gap between marks in `.axi-ticks` | `3px` | `<div class="axi-ticks" style="--axi-ticks-gap: 2px">` |
|
|
86
88
|
|
|
87
89
|
`--axi-page-pad: 0` is the one to know about: it is how a measure nested
|
|
88
90
|
inside another measure avoids paying the gutter twice.
|
package/dist/axi.css
CHANGED
|
@@ -157,6 +157,8 @@ a { color: inherit; }
|
|
|
157
157
|
.axi-pill:hover,
|
|
158
158
|
.axi-pill[aria-pressed="true"]:hover,
|
|
159
159
|
.axi-select:hover,
|
|
160
|
+
.axi-picker__btn:hover,
|
|
161
|
+
.axi-picker__btn[aria-expanded="true"],
|
|
160
162
|
.axi-card:hover,
|
|
161
163
|
.axi-drawer__close:hover {
|
|
162
164
|
transform: none !important;
|
|
@@ -400,8 +402,11 @@ a { color: inherit; }
|
|
|
400
402
|
/* ---------- select ---------- */
|
|
401
403
|
/* The closed box is ours everywhere: strip the native control and draw the
|
|
402
404
|
caret, so a select sits alongside the other controls as just another
|
|
403
|
-
outlined chip instead of announcing the OS.
|
|
404
|
-
|
|
405
|
+
outlined chip instead of announcing the OS. .axi-picker__btn is the same
|
|
406
|
+
box worn by a button instead of a <select>; the two share every
|
|
407
|
+
declaration here so a dropdown reads the same whichever half opens it. */
|
|
408
|
+
.axi-select,
|
|
409
|
+
.axi-picker__btn {
|
|
405
410
|
appearance: none;
|
|
406
411
|
padding: 10px 30px 10px 9px;
|
|
407
412
|
border: var(--axi-border-control) solid var(--axi-ink-line);
|
|
@@ -422,7 +427,13 @@ a { color: inherit; }
|
|
|
422
427
|
background-repeat: no-repeat;
|
|
423
428
|
transition: transform .1s, box-shadow .1s;
|
|
424
429
|
}
|
|
425
|
-
.
|
|
430
|
+
/* The open trigger keeps the lift. Rule 4 gives the raise to a pointer, but a
|
|
431
|
+
disclosure that drops back flat the moment the pointer moves into the list
|
|
432
|
+
it opened severs the two: the lift is what says this box and that popover
|
|
433
|
+
are one control. */
|
|
434
|
+
.axi-select:hover,
|
|
435
|
+
.axi-picker__btn:hover,
|
|
436
|
+
.axi-picker__btn[aria-expanded='true'] {
|
|
426
437
|
color: var(--axi-text);
|
|
427
438
|
box-shadow: var(--axi-offset-control) var(--axi-offset-control) 0 var(--axi-ink-line);
|
|
428
439
|
transform: translate(-2px, -2px);
|
|
@@ -431,7 +442,9 @@ a { color: inherit; }
|
|
|
431
442
|
/* The popup stays OS chrome until a browser lets us style it. Where one does
|
|
432
443
|
(Chromium's base-select), the list is drawn with the same outline and offset
|
|
433
444
|
block as our own popovers, so both dropdown kinds read as one family; where
|
|
434
|
-
it does not, the closed box above is still ours and the list is native
|
|
445
|
+
it does not, the closed box above is still ours and the list is native -
|
|
446
|
+
and if that native list is not good enough, .axi-picker below draws the
|
|
447
|
+
whole thing. */
|
|
435
448
|
@supports (appearance: base-select) {
|
|
436
449
|
/* base-select draws its own ::picker-icon, so the hand-drawn caret above
|
|
437
450
|
would be a second arrow. */
|
|
@@ -466,6 +479,101 @@ a { color: inherit; }
|
|
|
466
479
|
.axi-select option::checkmark { content: "\2713"; color: var(--axi-accent); font-weight: 900; }
|
|
467
480
|
}
|
|
468
481
|
|
|
482
|
+
/* ---------- picker ---------- */
|
|
483
|
+
/* The select's other half. Above, the native popup is left as OS chrome
|
|
484
|
+
wherever `appearance: base-select` is missing - and that is most places
|
|
485
|
+
today, including every Electron built on a Chromium older than the
|
|
486
|
+
property. What lands there is a raised list the language cannot reach: no
|
|
487
|
+
ink outline, no offset block, its own selection colour instead of the
|
|
488
|
+
accent. That is rule 3 broken by a box we do not own, so the fix is to stop
|
|
489
|
+
asking the OS to draw it. .axi-picker is a button and a popover wearing the
|
|
490
|
+
closed box and the list styling from the @supports branch above, so the two
|
|
491
|
+
kinds are the same dropdown and a consumer picks by what the platform has.
|
|
492
|
+
|
|
493
|
+
Prefer the native <select> where it works: it comes with keyboard handling,
|
|
494
|
+
typeahead, and a popup that can escape the window. This is what you use
|
|
495
|
+
when it does not.
|
|
496
|
+
|
|
497
|
+
Markup contract - the listbox pattern, and the popover carries the panel
|
|
498
|
+
weight because it is a raised surface, not a control:
|
|
499
|
+
|
|
500
|
+
<div class="axi-picker">
|
|
501
|
+
<button class="axi-picker__btn" aria-haspopup="listbox"
|
|
502
|
+
aria-expanded="false" aria-controls="months">Jul 2026</button>
|
|
503
|
+
<div class="axi-picker__pop" id="months" role="listbox" hidden>
|
|
504
|
+
<button class="axi-picker__opt" role="option" aria-selected="true">
|
|
505
|
+
Jul 2026
|
|
506
|
+
</button>
|
|
507
|
+
</div>
|
|
508
|
+
</div>
|
|
509
|
+
|
|
510
|
+
Like the menu and the tooltip, the language ships no script: `hidden`,
|
|
511
|
+
`aria-expanded` and `aria-selected` are the whole state, and the consumer
|
|
512
|
+
toggles them. gallery.js is the reference wiring, arrow keys and Escape
|
|
513
|
+
included. */
|
|
514
|
+
.axi-picker { position: relative; display: inline-flex; }
|
|
515
|
+
.axi-picker__btn {
|
|
516
|
+
display: inline-flex; align-items: center;
|
|
517
|
+
width: 100%;
|
|
518
|
+
text-align: left;
|
|
519
|
+
}
|
|
520
|
+
.axi-picker__pop {
|
|
521
|
+
/* Above .axi-mast's z-index: 40, for the same reason .axi-menu__pop is. */
|
|
522
|
+
position: absolute; top: calc(100% + 9px); left: 0; z-index: 41;
|
|
523
|
+
/* Never narrower than the box it came out of, and no wider than its
|
|
524
|
+
longest row needs. A list that shrinks to the text is a list that has
|
|
525
|
+
moved, and the eye has to find the column again. */
|
|
526
|
+
min-width: 100%;
|
|
527
|
+
max-height: 340px; overflow-y: auto;
|
|
528
|
+
padding: 6px;
|
|
529
|
+
background: var(--axi-surface-raised);
|
|
530
|
+
border: var(--axi-border-panel) solid var(--axi-ink-line);
|
|
531
|
+
border-radius: var(--axi-radius);
|
|
532
|
+
/* The block falls outside the popover's own box. An ancestor that clips -
|
|
533
|
+
a scrolling pane, a panel with overflow: hidden - eats it, and the only
|
|
534
|
+
shadow on the screen is the one that goes missing. */
|
|
535
|
+
box-shadow: var(--axi-offset-panel) var(--axi-offset-panel) 0 var(--axi-ink-line);
|
|
536
|
+
}
|
|
537
|
+
.axi-picker__pop[hidden] { display: none; }
|
|
538
|
+
/* A popover cannot always live beside its trigger. Inside a pane that scrolls
|
|
539
|
+
or a panel that clips, `position: absolute` puts the list where the
|
|
540
|
+
overflow can eat it - and the offset block falls outside the popover's box,
|
|
541
|
+
so the block is the first thing to go. The way out is the tooltip's
|
|
542
|
+
contract: append the popover to <body> and set left/top from script, having
|
|
543
|
+
measured the trigger. The box is unchanged; only who positions it moves. */
|
|
544
|
+
.axi-picker__pop--fixed { position: fixed; }
|
|
545
|
+
.axi-picker__opt {
|
|
546
|
+
display: flex; align-items: center; gap: 9px;
|
|
547
|
+
width: 100%;
|
|
548
|
+
padding: 8px 9px;
|
|
549
|
+
border: 0;
|
|
550
|
+
border-radius: var(--axi-radius-sm);
|
|
551
|
+
background: transparent;
|
|
552
|
+
color: var(--axi-text-dim);
|
|
553
|
+
font: var(--axi-t-label);
|
|
554
|
+
font-size: 12.5px;
|
|
555
|
+
letter-spacing: var(--axi-ls-label);
|
|
556
|
+
text-transform: uppercase;
|
|
557
|
+
text-align: left;
|
|
558
|
+
cursor: pointer;
|
|
559
|
+
}
|
|
560
|
+
/* The tick is in every row, inked only in the chosen one. Give it to the
|
|
561
|
+
selected row alone and every label shifts by its width as the choice moves,
|
|
562
|
+
which turns picking an option into the list twitching. */
|
|
563
|
+
.axi-picker__opt::before {
|
|
564
|
+
content: "\2713";
|
|
565
|
+
color: transparent;
|
|
566
|
+
font-weight: 900;
|
|
567
|
+
}
|
|
568
|
+
.axi-picker__opt:hover, .axi-picker__opt:focus {
|
|
569
|
+
background: var(--axi-ground); color: var(--axi-text);
|
|
570
|
+
}
|
|
571
|
+
/* The page-wide focus ring sits 2px outside its element; inside a popover
|
|
572
|
+
this tight that is 2px into the neighbouring row, so pull it back in. */
|
|
573
|
+
.axi-picker__opt:focus-visible { outline-offset: -3px; }
|
|
574
|
+
.axi-picker__opt[aria-selected='true'] { color: var(--axi-text); }
|
|
575
|
+
.axi-picker__opt[aria-selected='true']::before { color: var(--axi-accent); }
|
|
576
|
+
|
|
469
577
|
/* --- layout.css --- */
|
|
470
578
|
/* axi design language - layout.
|
|
471
579
|
Three measures, one grid, two spacing helpers. Deliberately small: a
|
|
@@ -1028,6 +1136,42 @@ a { color: inherit; }
|
|
|
1028
1136
|
background: var(--axi-series, var(--axi-accent));
|
|
1029
1137
|
}
|
|
1030
1138
|
|
|
1139
|
+
/* ---------- ticks ---------- */
|
|
1140
|
+
/* A run of yes/no along a timeline: attended or missed, passed or failed,
|
|
1141
|
+
shipped or skipped. This is rule 9 read the other way round - a bar has a
|
|
1142
|
+
length because a quantity has one, and "no" has no length at all. Drawn as
|
|
1143
|
+
a bar it has to be faked with a stub, and a stub reads as "a little bit",
|
|
1144
|
+
which is the same lie as a faded fill. So every event here is one mark of
|
|
1145
|
+
the same size, and what changes between them is only which ink it is drawn
|
|
1146
|
+
in: rule 10's two series, the accent for the fact and the neutral ramp for
|
|
1147
|
+
its absence.
|
|
1148
|
+
|
|
1149
|
+
Neither mark is outlined, which is the one place this departs from the
|
|
1150
|
+
filled/empty pair the language reaches for first. At the size a run of
|
|
1151
|
+
twenty sits in a table row the outline IS the mark - three pixels of edge
|
|
1152
|
+
around one pixel of middle - so the pair would differ in nothing the eye
|
|
1153
|
+
can resolve. A strip drawn large enough to outline is a bar chart, and
|
|
1154
|
+
.axi-bars already is one.
|
|
1155
|
+
|
|
1156
|
+
Marks are a fixed width and never flex: a run of fourteen and a run of
|
|
1157
|
+
three share a column in a table, and the short one has to read as a short
|
|
1158
|
+
run rather than as fourteen fatter events. */
|
|
1159
|
+
.axi-ticks {
|
|
1160
|
+
display: inline-flex;
|
|
1161
|
+
align-items: stretch;
|
|
1162
|
+
gap: var(--axi-ticks-gap, 3px);
|
|
1163
|
+
height: var(--axi-tick-h, 15px);
|
|
1164
|
+
}
|
|
1165
|
+
.axi-ticks__tick {
|
|
1166
|
+
flex: none;
|
|
1167
|
+
width: var(--axi-tick-w, 5px);
|
|
1168
|
+
background: var(--axi-rule);
|
|
1169
|
+
border-radius: var(--axi-radius-sm);
|
|
1170
|
+
}
|
|
1171
|
+
.axi-ticks__tick--on {
|
|
1172
|
+
background: var(--axi-series, var(--axi-accent));
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1031
1175
|
/* ---------- plot ---------- */
|
|
1032
1176
|
/* A frame for a line or an area, with its horizontal rules drawn in. The
|
|
1033
1177
|
rules are hard stops in a repeating gradient, which is the select caret's
|
package/docs/RULES.md
CHANGED
|
@@ -111,6 +111,17 @@ read `--axi-radius`, everything control-sized reads `--axi-radius-sm`, and
|
|
|
111
111
|
`tests/tokens.test.mjs` fails on a literal radius in a component file the same
|
|
112
112
|
way it fails on a literal border weight.
|
|
113
113
|
|
|
114
|
+
**A box the OS draws is a box that breaks this.** A native `<select>` popup is
|
|
115
|
+
a raised list the language cannot reach: no ink outline, no offset block, and
|
|
116
|
+
its own selection colour where the accent belongs. `.axi-select` styles the
|
|
117
|
+
closed box and hands the list to `appearance: base-select` where the browser
|
|
118
|
+
has it — but most do not yet, and an Electron app is pinned to whatever
|
|
119
|
+
Chromium its version shipped. `.axi-picker` is the way out: the same closed
|
|
120
|
+
box on a button, and the list drawn as a popover that takes the panel weight
|
|
121
|
+
like any other raised surface. Reach for the native select first, because it
|
|
122
|
+
brings keyboard handling and a popup that can leave the window; reach for the
|
|
123
|
+
picker when the popup it opens is not one this rule can touch.
|
|
124
|
+
|
|
114
125
|
## 4. Hover lifts
|
|
115
126
|
|
|
116
127
|
The lift is per form step, not one universal number: a control has no resting
|
|
@@ -247,6 +258,12 @@ is the one chart type that cannot be built without the thing rule 2 forbids, so
|
|
|
247
258
|
a distribution is drawn as bars, or as a table sorted by the value, or not at
|
|
248
259
|
all.
|
|
249
260
|
|
|
261
|
+
The other corollary is that a thing with no quantity gets no length. A run of
|
|
262
|
+
yes/no — attended or missed, passed or failed — is a sequence of facts, and a
|
|
263
|
+
fact has no magnitude to draw: rendering "no" as a short bar says "a little
|
|
264
|
+
bit" as loudly as a faded fill says "30%". That series is a row of marks of
|
|
265
|
+
one size, differing only in ink, which is `.axi-ticks`.
|
|
266
|
+
|
|
250
267
|
## 10. A chart's ink is the accent
|
|
251
268
|
|
|
252
269
|
One series is the accent. A second, for comparison, is the neutral ramp —
|