@axiapps/axi-design 1.43.0 → 1.45.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 +6 -0
- package/dist/axi.css +710 -46
- package/dist/themes/flat.css +1 -0
- package/docs/RULES.md +173 -4
- package/package.json +1 -1
- package/src/base.css +40 -0
- package/src/data.css +333 -2
- package/src/layout.css +68 -0
- package/src/primitives.css +123 -8
- package/src/shells.css +70 -29
- package/src/tokens.css +14 -0
- package/src/utilities.css +62 -7
package/dist/axi.css
CHANGED
|
@@ -122,6 +122,20 @@
|
|
|
122
122
|
--axi-ink-on-fill below, on the other side of the same token. */
|
|
123
123
|
--axi-ground-deep: var(--axi-ink-line);
|
|
124
124
|
--axi-rule: #3a4250; /* internal rules, inside an outlined panel */
|
|
125
|
+
/* A gridline is quieter than a rule. A rule divides two things; a grid is
|
|
126
|
+
scaffolding a value is read against, and it has to sit barely above the
|
|
127
|
+
ground it is drawn on or the chart becomes a grid of boxes with numbers
|
|
128
|
+
in them - the failure rule 3 names for tables, arriving in a plot.
|
|
129
|
+
|
|
130
|
+
Derived, not picked, and that is the point of its being a token. The one
|
|
131
|
+
consumer that had drawn a chart grid wrote #262c36 for it: the default
|
|
132
|
+
rule laid over the default ground at 47%, to within one step per channel,
|
|
133
|
+
and a fixed slate that stayed slate under glass and under flat while both
|
|
134
|
+
themes relit their rules. Mixing toward transparent rather than toward
|
|
135
|
+
the ground keeps it right on a panel fill too, and keeps it following
|
|
136
|
+
--axi-rule when a theme restates that - so a theme has nothing to say
|
|
137
|
+
here, and says nothing. */
|
|
138
|
+
--axi-grid: color-mix(in srgb, var(--axi-rule) 47%, transparent);
|
|
125
139
|
/* --- the well --- */
|
|
126
140
|
/* A field sunk INTO a surface rather than raised off one: a table picker's
|
|
127
141
|
list, a bar's trough, a column of cards standing on the page. The language
|
|
@@ -347,6 +361,46 @@ a { color: inherit; }
|
|
|
347
361
|
}
|
|
348
362
|
}
|
|
349
363
|
|
|
364
|
+
/* The scrollbar is chrome, and chrome is drawn in the language's own ink. A
|
|
365
|
+
browser's default bar is a light capsule on a light channel, which is two
|
|
366
|
+
things this language does not have: a colour at partial opacity over the
|
|
367
|
+
ground (rule 2) and the one radius the system refuses (see --axi-radius).
|
|
368
|
+
The consumer that first themed it had written both anyway - a 999px thumb
|
|
369
|
+
in white at 35% - in its own stylesheet, because the language said nothing
|
|
370
|
+
here, and a decision the language leaves unsaid is one every consumer makes
|
|
371
|
+
differently.
|
|
372
|
+
|
|
373
|
+
So: the thumb is the rule ink, a rectangle, in a transparent channel. The
|
|
374
|
+
channel is transparent rather than the ground for the reason the well's fill
|
|
375
|
+
gives: an opaque page colour inside a translucent pane is a black patch, and
|
|
376
|
+
a bar runs down the inside of panes and wells as often as down the page.
|
|
377
|
+
|
|
378
|
+
Two spellings, one per engine, and neither is a fallback for the other.
|
|
379
|
+
`::-webkit-scrollbar` is the only spelling that controls the SHAPE - the
|
|
380
|
+
standard `scrollbar-color` pair recolours whatever the engine draws, and
|
|
381
|
+
what Chromium draws is a capsule with arrow buttons, the shape this language
|
|
382
|
+
refuses. Worse, Chromium (121+) switches the pseudo-elements off entirely the
|
|
383
|
+
moment either standard property is set, so writing both does not combine
|
|
384
|
+
them: it hands Chromium the capsule and throws the rectangle away. (The
|
|
385
|
+
consumer this replaces had written both, and had been tuning a webkit block
|
|
386
|
+
Chromium stopped reading, for years.) So the standard pair is scoped to the
|
|
387
|
+
engines that have no pseudo-element to disable, which today is Firefox, and
|
|
388
|
+
the pseudo-elements carry the design everywhere else. scrollbar-color
|
|
389
|
+
inherits, so the root says it once; scrollbar-width does not, so it is set
|
|
390
|
+
on everything.
|
|
391
|
+
|
|
392
|
+
The quiet-scroll rule in utilities.css still wins where it applies: those
|
|
393
|
+
selectors carry a class and these carry none, so a strip that has asked for
|
|
394
|
+
no bar gets none, and everything else gets this one. */
|
|
395
|
+
@supports not selector(::-webkit-scrollbar) {
|
|
396
|
+
html { scrollbar-color: var(--axi-rule) transparent; }
|
|
397
|
+
* { scrollbar-width: thin; }
|
|
398
|
+
}
|
|
399
|
+
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
400
|
+
::-webkit-scrollbar-track,
|
|
401
|
+
::-webkit-scrollbar-corner { background: transparent; }
|
|
402
|
+
::-webkit-scrollbar-thumb { background: var(--axi-rule); border-radius: 0; }
|
|
403
|
+
|
|
350
404
|
/* --- primitives.css --- */
|
|
351
405
|
/* axi design language - primitives.
|
|
352
406
|
The pieces that recur in every property. Two rules govern the whole file:
|
|
@@ -425,13 +479,91 @@ a.axi-panel--tile:hover,
|
|
|
425
479
|
button.axi-panel--tile:hover,
|
|
426
480
|
.axi-panel--tile[role='button']:hover { box-shadow: var(--axi-shadow-control-hover); }
|
|
427
481
|
|
|
482
|
+
/* The status cap rule 5 describes, as a thing you can write. A verdict about
|
|
483
|
+
the reading inside a panel, a tile or a stat is said once, across its head,
|
|
484
|
+
in the status ink - directly over the value it judges - and the rest of the
|
|
485
|
+
outline stays the ink line every other edge in the language is drawn in. Not
|
|
486
|
+
down the left: a full-height stripe is the box's frame, and five tiles in a
|
|
487
|
+
row become five coloured frames saying nothing about any one number.
|
|
488
|
+
|
|
489
|
+
It recolours the top edge the element already has rather than adding a bar,
|
|
490
|
+
so it is drawn at the element's own weight by construction - the panel step
|
|
491
|
+
on a panel, the control step on a tile or a stat - which is rule 5's own
|
|
492
|
+
argument against a heavy cap on a light frame. The state is an attribute the
|
|
493
|
+
consumer declares about its data (rule 13), read by the three objects a
|
|
494
|
+
reading lives in; a card's strip is a different claim (which category) and
|
|
495
|
+
keeps its own word.
|
|
496
|
+
|
|
497
|
+
Four values and no fifth: the accent is not a verdict, and a cap in it would
|
|
498
|
+
say "picked" about a thing nobody picked. */
|
|
499
|
+
.axi-panel[data-status="ok"], .axi-stat[data-status="ok"] { border-top-color: var(--axi-ok); }
|
|
500
|
+
.axi-panel[data-status="warn"], .axi-stat[data-status="warn"] { border-top-color: var(--axi-warn); }
|
|
501
|
+
.axi-panel[data-status="danger"], .axi-stat[data-status="danger"] { border-top-color: var(--axi-danger); }
|
|
502
|
+
.axi-panel[data-status="meta"], .axi-stat[data-status="meta"] { border-top-color: var(--axi-meta); }
|
|
503
|
+
|
|
504
|
+
/* The one of these you picked. A grid of panels, tiles or cards where one is
|
|
505
|
+
chosen - a theme picker, a fight picker, a leader you have opened - and the
|
|
506
|
+
surface has to say which.
|
|
507
|
+
|
|
508
|
+
Not the accent fill a rail item takes. A rail item is a word, and a word can
|
|
509
|
+
be printed on the accent; a panel carries content, and an accent fill drowns
|
|
510
|
+
it. The answer is the third weight .axi-rail__nav--quiet already found for
|
|
511
|
+
the same reason: the surface rises and the accent arrives on the edge. A
|
|
512
|
+
whole edge here rather than the leading one, because a grid is read in two
|
|
513
|
+
directions and has no leading side to light.
|
|
514
|
+
|
|
515
|
+
Keyed off what the element already says, the way .axi-panel--tile keys its
|
|
516
|
+
press off being an <a> or a <button> rather than off a second class. The
|
|
517
|
+
language has one spelling per selection semantics, and this rule takes all
|
|
518
|
+
of them rather than inventing a fourth: `aria-current` for a place you
|
|
519
|
+
navigated to (a rail item, a tab, a table row), `aria-pressed` for a toggle
|
|
520
|
+
(a pill), `aria-selected` for an option in a listbox (a picker option). A
|
|
521
|
+
consumer that marks a card by appending a border utility has told assistive
|
|
522
|
+
tech nothing at all, and that is the defect this replaces rather than the
|
|
523
|
+
missing fill - ten sites in one consumer, six different bases, three
|
|
524
|
+
invented extra marks, and not one of them announced.
|
|
525
|
+
|
|
526
|
+
The fourth arm is a <label> wrapping its own radio, which is the correct
|
|
527
|
+
markup for a picker made of panels: the input carries the state, so the
|
|
528
|
+
label has no attribute to set and copying the state onto one would be a
|
|
529
|
+
second source of truth that can disagree. This is the language's only
|
|
530
|
+
:has(), and the child combinator is the whole point - `> input:checked`
|
|
531
|
+
means this panel IS the control, where a descendant match would light a
|
|
532
|
+
panel up for any checkbox buried in its content.
|
|
533
|
+
|
|
534
|
+
Both spellings on one rule, per "a style only reachable through a layer will
|
|
535
|
+
be re-invented": .axi-card is a shell and .axi-panel a primitive, and a
|
|
536
|
+
consumer marking one of them must not have to find out which file its
|
|
537
|
+
selected state was written in. The fill is the `background` shorthand and
|
|
538
|
+
not the -paint companion, because --axi-surface-raised is a surface token
|
|
539
|
+
and a theme is allowed to make a surface a gradient. */
|
|
540
|
+
:is(.axi-card, .axi-panel):is(
|
|
541
|
+
[aria-current],
|
|
542
|
+
[aria-pressed='true'],
|
|
543
|
+
[aria-selected='true'],
|
|
544
|
+
:has(> input:checked)
|
|
545
|
+
) {
|
|
546
|
+
background: var(--axi-surface-raised);
|
|
547
|
+
border-color: var(--axi-accent);
|
|
548
|
+
}
|
|
549
|
+
|
|
428
550
|
/* ---------- well ---------- */
|
|
429
551
|
/* The panel's inverse: a field sunk into the surface around it rather than
|
|
430
552
|
raised off it. No block, because a recess casts nothing, and the rule rather
|
|
431
553
|
than the ink for its edge - see --axi-well-fill in src/tokens.css for why
|
|
432
554
|
both of those follow from the same fact. Reach for this wherever a second
|
|
433
555
|
panel inside a panel was the instinct: a picker list beside a table, a column
|
|
434
|
-
of cards standing on the page, a trough a bar fills.
|
|
556
|
+
of cards standing on the page, a trough a bar fills.
|
|
557
|
+
|
|
558
|
+
.axi-split__nav is the first of those three named as a part rather than left
|
|
559
|
+
to the consumer to remember, and it is in THIS rule rather than restating the
|
|
560
|
+
fill next to the split pane. Two rules that agree today are two rules that
|
|
561
|
+
disagree after the next edit - see "A style only reachable through a layer
|
|
562
|
+
will be re-invented" in docs/RULES.md, which is the same argument arriving
|
|
563
|
+
from the other direction. The split pane's slot cannot be a well by
|
|
564
|
+
convention: a consumer who forgets the second class gets a picker with no
|
|
565
|
+
recess, and the language has no way to notice. */
|
|
566
|
+
.axi-split__nav,
|
|
435
567
|
.axi-well {
|
|
436
568
|
background: var(--axi-well-fill);
|
|
437
569
|
border: var(--axi-border-control) solid var(--axi-well-line);
|
|
@@ -530,6 +662,43 @@ button.axi-panel--tile:hover,
|
|
|
530
662
|
icon-only button has no label to pad around, so the 20px sides meant for
|
|
531
663
|
one produced a wide rectangle around a single glyph - visible in this
|
|
532
664
|
language's own gallery, in the example titled "Alone in a button". */
|
|
665
|
+
/* Icon over label rather than beside it. Not a style choice - it is the only
|
|
666
|
+
thing that makes a bar of equal actions fit a phone. Measured on the
|
|
667
|
+
consumer that needed it: four icon+label actions side by side want 387px of
|
|
668
|
+
the 337px available at 393px wide, and every label is one unbreakable word,
|
|
669
|
+
so min-content equals max-content and flex-shrink has nothing to give. The
|
|
670
|
+
last action simply runs off the screen. Stacked, the same four come to
|
|
671
|
+
~291px.
|
|
672
|
+
|
|
673
|
+
min-width: 0 because that failure has a second half. A flex item's default
|
|
674
|
+
min-width is its content, so a button in a row refuses to shrink below its
|
|
675
|
+
own label even when the row is told to divide the space - which is the same
|
|
676
|
+
trap .axi-tabs__tab documents for its label and .axi-split__body for its
|
|
677
|
+
track.
|
|
678
|
+
|
|
679
|
+
The label is addressed as an element because a stacked button has two
|
|
680
|
+
children and only one of them is words; naming a part would make every
|
|
681
|
+
consumer add a class to say what the span already says. :where() so it
|
|
682
|
+
spends no specificity - the ink layer and any modifier still out-rank it,
|
|
683
|
+
which is the contract src/utilities.css is owed. */
|
|
684
|
+
.axi-btn--stack {
|
|
685
|
+
flex-direction: column;
|
|
686
|
+
gap: 5px;
|
|
687
|
+
min-width: 0;
|
|
688
|
+
}
|
|
689
|
+
.axi-btn--stack > :where(span) {
|
|
690
|
+
/* Both, and the pair is the point: max-width caps it against the button,
|
|
691
|
+
min-width: 0 is what lets it shrink at all. A flex item's default
|
|
692
|
+
min-width is its content, so max-width alone leaves the ellipsis
|
|
693
|
+
unreachable - the label overflows the cap instead of truncating inside
|
|
694
|
+
it. tests/tokens.test.mjs pins the pair for every truncating rule in the
|
|
695
|
+
language, and it caught this one. */
|
|
696
|
+
max-width: 100%;
|
|
697
|
+
min-width: 0;
|
|
698
|
+
overflow: hidden;
|
|
699
|
+
text-overflow: ellipsis;
|
|
700
|
+
white-space: nowrap;
|
|
701
|
+
}
|
|
533
702
|
.axi-btn--sm { --axi-btn-pad: 8px 13px; --axi-btn-size: 12px; }
|
|
534
703
|
.axi-btn--xs { --axi-btn-pad: 5px 9px; --axi-btn-size: 11px; }
|
|
535
704
|
.axi-btn--icon { --axi-btn-pad: 7px; }
|
|
@@ -828,7 +997,7 @@ button.axi-panel--tile:hover,
|
|
|
828
997
|
says `axi-link axi-ink-dim` must stay dim under the cursor, and the plain
|
|
829
998
|
`:hover` form is what made the danger button read white under one. */
|
|
830
999
|
.axi-link,
|
|
831
|
-
.axi-prose a {
|
|
1000
|
+
.axi-prose :where(a) {
|
|
832
1001
|
padding: 0;
|
|
833
1002
|
background: none;
|
|
834
1003
|
border: 0;
|
|
@@ -841,7 +1010,7 @@ button.axi-panel--tile:hover,
|
|
|
841
1010
|
cursor: pointer;
|
|
842
1011
|
}
|
|
843
1012
|
.axi-link:where(:hover),
|
|
844
|
-
.axi-prose
|
|
1013
|
+
.axi-prose :where(a:hover) { color: var(--axi-text); }
|
|
845
1014
|
|
|
846
1015
|
/* ---------- quiet action ---------- */
|
|
847
1016
|
/* A control that is only its label. Not a button - it draws no box, takes no
|
|
@@ -967,8 +1136,8 @@ button.axi-panel--tile:hover,
|
|
|
967
1136
|
disclosure that drops back flat the moment the pointer moves into the list
|
|
968
1137
|
it opened severs the two: the lift is what says this box and that popover
|
|
969
1138
|
are one control. */
|
|
970
|
-
.axi-select:hover,
|
|
971
|
-
.axi-picker__btn:hover,
|
|
1139
|
+
.axi-select:where(:hover),
|
|
1140
|
+
.axi-picker__btn:where(:hover),
|
|
972
1141
|
.axi-picker__btn[aria-expanded='true'] {
|
|
973
1142
|
color: var(--axi-text);
|
|
974
1143
|
box-shadow: var(--axi-shadow-control);
|
|
@@ -995,7 +1164,7 @@ button.axi-panel--tile:hover,
|
|
|
995
1164
|
backdrop-filter: var(--axi-surface-filter);
|
|
996
1165
|
box-shadow: var(--axi-shadow-panel);
|
|
997
1166
|
}
|
|
998
|
-
.axi-select option {
|
|
1167
|
+
.axi-select :where(option) {
|
|
999
1168
|
display: flex; align-items: center; gap: 9px;
|
|
1000
1169
|
padding: 8px 9px;
|
|
1001
1170
|
border-radius: var(--axi-radius-sm);
|
|
@@ -1010,7 +1179,7 @@ button.axi-panel--tile:hover,
|
|
|
1010
1179
|
level's fill - and this popover is filled with --axi-surface-raised, which
|
|
1011
1180
|
a theme may take translucent, so an opaque row here is the black patch
|
|
1012
1181
|
--axi-well-fill exists to prevent. Same for the picker's rows below. */
|
|
1013
|
-
.axi-select option:hover,
|
|
1182
|
+
.axi-select :where(option:hover, option:focus) {
|
|
1014
1183
|
background: var(--axi-well-fill); color: var(--axi-text);
|
|
1015
1184
|
}
|
|
1016
1185
|
/* The page-wide focus ring sits 2px outside its element; inside a picker
|
|
@@ -1109,7 +1278,7 @@ button.axi-panel--tile:hover,
|
|
|
1109
1278
|
color: transparent;
|
|
1110
1279
|
font-weight: 900;
|
|
1111
1280
|
}
|
|
1112
|
-
.axi-picker__opt:hover,
|
|
1281
|
+
.axi-picker__opt:where(:hover, :focus) {
|
|
1113
1282
|
background: var(--axi-well-fill); color: var(--axi-text);
|
|
1114
1283
|
}
|
|
1115
1284
|
/* The page-wide focus ring sits 2px outside its element; inside a popover
|
|
@@ -1296,6 +1465,68 @@ textarea.axi-input {
|
|
|
1296
1465
|
/* A horizontal run that wraps rather than overflowing. */
|
|
1297
1466
|
.axi-row { display: flex; align-items: center; flex-wrap: wrap; gap: var(--axi-row-gap, 10px); }
|
|
1298
1467
|
|
|
1468
|
+
/* ---------- split pane ---------- */
|
|
1469
|
+
/* A picker on the left choosing what the surface on the right shows. Thirteen
|
|
1470
|
+
sections of one consumer are this shape, and a fourteenth had hand-built it
|
|
1471
|
+
inline because there was no name for it.
|
|
1472
|
+
|
|
1473
|
+
It is two objects on one plane and not two panels, which is the whole reason
|
|
1474
|
+
it needs naming: a picker list beside a table is the first case --axi-well's
|
|
1475
|
+
own comment names, and the thing the list picks is the raised counterpart to
|
|
1476
|
+
that recess. Drawn as two panels it read flat - a hairline parting one field,
|
|
1477
|
+
with the list's scrollbar carving a groove down the middle of it - because
|
|
1478
|
+
nothing in it was an object.
|
|
1479
|
+
|
|
1480
|
+
The body carries the CONTROL step, not the panel's. A split pane lives
|
|
1481
|
+
inside a panel that has already paid a 6px block, and rule 8's counterpart
|
|
1482
|
+
settles what a second one nested in it reads as: two planes arguing. The
|
|
1483
|
+
consumer that derived this shape by hand got the outline and left out the
|
|
1484
|
+
block entirely, which is a rule 3 failure in the other direction - a
|
|
1485
|
+
boundary drawn around content that is standing on the surface behind it.
|
|
1486
|
+
Outlined and blocked, at the weight that says "the contents of a box".
|
|
1487
|
+
|
|
1488
|
+
The radius follows the form step and not the footprint. The body is
|
|
1489
|
+
panel-sized in area and control-weight in form, and .axi-panel--tile already
|
|
1490
|
+
settled that argument the same way.
|
|
1491
|
+
|
|
1492
|
+
`minmax(0, 1fr)` rather than `1fr`: a table in a `1fr` track cannot shrink
|
|
1493
|
+
below its own content, and every cell in .axi-table is `nowrap`, so the
|
|
1494
|
+
column blows out and takes the pane's width with it. `1fr` is
|
|
1495
|
+
`minmax(auto, 1fr)` and `auto` is a content floor.
|
|
1496
|
+
|
|
1497
|
+
No breakpoint of its own. Under 640px the two stack, which is layout.css's
|
|
1498
|
+
one breakpoint and deliberately still its only one - .axi-table--fixed
|
|
1499
|
+
already makes a 360px pane work, so a pane narrow enough to need a second
|
|
1500
|
+
breakpoint is one --fixed was written for. */
|
|
1501
|
+
.axi-split {
|
|
1502
|
+
display: grid;
|
|
1503
|
+
grid-template-columns: var(--axi-split-nav-w, 280px) minmax(0, 1fr);
|
|
1504
|
+
gap: var(--axi-split-gap, 10px);
|
|
1505
|
+
/* A split pane is routinely the flex child that fills a section's height,
|
|
1506
|
+
and a grid that cannot shrink below its content pushes the section past
|
|
1507
|
+
its own cap - the same reason .axi-palette__list says it. */
|
|
1508
|
+
min-height: 0;
|
|
1509
|
+
}
|
|
1510
|
+
/* The picker is a well, declared as one in the same rule as .axi-well in
|
|
1511
|
+
src/primitives.css rather than restated here. This part only says what the
|
|
1512
|
+
slot adds: it scrolls, and it may not out-grow the row it sits in. Its
|
|
1513
|
+
scrollbar is hidden by the language's quiet-scroll rule in
|
|
1514
|
+
src/utilities.css, which is where that decision lives for every strip. */
|
|
1515
|
+
.axi-split__nav { overflow-y: auto; min-height: 0; }
|
|
1516
|
+
.axi-split__body {
|
|
1517
|
+
/* Paired with the track's minmax(0, 1fr): the track stops the COLUMN
|
|
1518
|
+
growing, this stops the ITEM, whose min-width is `auto` by default and is
|
|
1519
|
+
what an ellipsis inside it is actually fighting. */
|
|
1520
|
+
min-width: 0;
|
|
1521
|
+
min-height: 0;
|
|
1522
|
+
overflow: hidden;
|
|
1523
|
+
background: var(--axi-surface);
|
|
1524
|
+
backdrop-filter: var(--axi-surface-filter);
|
|
1525
|
+
border: var(--axi-border-control) solid var(--axi-ink-line);
|
|
1526
|
+
border-radius: var(--axi-radius-sm);
|
|
1527
|
+
box-shadow: var(--axi-shadow-control);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1299
1530
|
@media (max-width: 640px) {
|
|
1300
1531
|
/* The fallback must match the resting rule's (var(--axi-gutter), 18px) -
|
|
1301
1532
|
README.md documents --axi-page-pad's fallback as --axi-gutter, and a
|
|
@@ -1303,6 +1534,12 @@ textarea.axi-input {
|
|
|
1303
1534
|
mobile, which is the one viewport where the gutter matters most. */
|
|
1304
1535
|
.axi-page { padding-inline: var(--axi-page-pad, var(--axi-gutter)); }
|
|
1305
1536
|
.axi-grid { grid-template-columns: 1fr; }
|
|
1537
|
+
/* The picker stops being a column beside the thing it picks and becomes the
|
|
1538
|
+
row above it. It keeps a height cap - a picker that grows to twenty rows
|
|
1539
|
+
pushes the result it picked off the screen, which is the one thing the
|
|
1540
|
+
stacked form must not do. */
|
|
1541
|
+
.axi-split { grid-template-columns: 1fr; }
|
|
1542
|
+
.axi-split__nav { max-height: var(--axi-split-nav-h, 200px); }
|
|
1306
1543
|
}
|
|
1307
1544
|
|
|
1308
1545
|
/* --- shells.css --- */
|
|
@@ -1311,14 +1548,23 @@ textarea.axi-input {
|
|
|
1311
1548
|
redefining them, so a change to .axi-panel moves the notice, the toolbar,
|
|
1312
1549
|
the popover and the card together. */
|
|
1313
1550
|
|
|
1314
|
-
/* The small uppercase heading that labels a panel's contents.
|
|
1551
|
+
/* The small uppercase heading that labels a panel's contents.
|
|
1552
|
+
|
|
1553
|
+
The gap under it is a knob because the eyebrow heads more than one size of
|
|
1554
|
+
thing. 14px is the figure for a panel at its resting padding; a tile is one
|
|
1555
|
+
form step down and its padding is one density step down with it, so the
|
|
1556
|
+
eyebrow inside one reads the tighter figure in the rule below, the same way
|
|
1557
|
+
.axi-panel--tile reads --axi-panel-pad at a tighter fallback of its own. An
|
|
1558
|
+
eyebrow sitting in a row - a sheet's head, a toolbar - wants no gap at all,
|
|
1559
|
+
and says so with the knob rather than with an inline margin. */
|
|
1315
1560
|
.axi-eyebrow {
|
|
1316
|
-
margin: 0 0 14px;
|
|
1561
|
+
margin: 0 0 var(--axi-eyebrow-gap, 14px);
|
|
1317
1562
|
font: var(--axi-t-eyebrow);
|
|
1318
1563
|
letter-spacing: var(--axi-ls-eyebrow);
|
|
1319
1564
|
text-transform: uppercase;
|
|
1320
1565
|
color: var(--axi-text-faint);
|
|
1321
1566
|
}
|
|
1567
|
+
.axi-panel--tile .axi-eyebrow { margin-bottom: var(--axi-eyebrow-gap, 6px); }
|
|
1322
1568
|
|
|
1323
1569
|
/* ---------- masthead ---------- */
|
|
1324
1570
|
/* Opaque, because content scrolls under it. Which is also why it paints the
|
|
@@ -1403,9 +1649,35 @@ textarea.axi-input {
|
|
|
1403
1649
|
it shrinks an item - so one tab too many sent the last item in source order
|
|
1404
1650
|
to a second row instead of taking width off the widest one. The strip is the
|
|
1405
1651
|
item that can give: it scrolls, and the row stays a row. */
|
|
1406
|
-
.axi-mast .axi-tabs { margin-left: auto;
|
|
1407
|
-
|
|
1408
|
-
|
|
1652
|
+
.axi-mast .axi-tabs { margin-left: auto; }
|
|
1653
|
+
/* A strip with more tabs than room scrolls rather than losing them.
|
|
1654
|
+
|
|
1655
|
+
.axi-tabs is `display: flex` with no wrap, so a strip too wide for its
|
|
1656
|
+
container overflows - and outside a scroll container the tabs past the edge
|
|
1657
|
+
are simply unreachable. The language already believed this twice and could
|
|
1658
|
+
say it in neither place a consumer could use: once here, behind .axi-mast,
|
|
1659
|
+
and once at the 640px breakpoint below, where EVERY strip scrolls. A
|
|
1660
|
+
standalone strip above that breakpoint with one tab too many had nowhere to
|
|
1661
|
+
look, and the consumer that hit it wrote `overflow-x: auto` on its own
|
|
1662
|
+
wrapper - the same re-invention docs/RULES.md describes, arriving a fourth
|
|
1663
|
+
time.
|
|
1664
|
+
|
|
1665
|
+
A modifier rather than the base rule, and this is the part worth arguing.
|
|
1666
|
+
`overflow-x: auto` computes `overflow-y` from `visible` to `auto`, so a
|
|
1667
|
+
strip that scrolls is also a strip that clips vertically - a focus ring, a
|
|
1668
|
+
badge hanging off a tab, a popover anchored to one. Every strip paying that
|
|
1669
|
+
to fix the strips that are too long is the wrong trade; the strips that are
|
|
1670
|
+
too long say so.
|
|
1671
|
+
|
|
1672
|
+
The bar is NOT hidden here, and the quiet-scroll rule in src/utilities.css
|
|
1673
|
+
deliberately does not name this. That rule is about a bar drawn down the
|
|
1674
|
+
side of a narrow strip, where the object's own edge is already a vertical
|
|
1675
|
+
line and a count elsewhere already says how many rows there are. A tab strip
|
|
1676
|
+
that scrolls horizontally has no second signal: the bar is the only thing
|
|
1677
|
+
saying there are more tabs. Same reason .axi-table__scroll keeps its. */
|
|
1678
|
+
.axi-tabs--scroll,
|
|
1679
|
+
.axi-mast .axi-tabs { flex-shrink: 1; min-width: 0; overflow-x: auto; }
|
|
1680
|
+
.axi-tabs :where(a, button) {
|
|
1409
1681
|
padding: 8px 16px;
|
|
1410
1682
|
font: 800 13.5px/1 var(--axi-sans);
|
|
1411
1683
|
color: var(--axi-text-dim); text-decoration: none;
|
|
@@ -1414,8 +1686,7 @@ textarea.axi-input {
|
|
|
1414
1686
|
border: var(--axi-border-control) solid transparent;
|
|
1415
1687
|
border-radius: var(--axi-radius-sm);
|
|
1416
1688
|
}
|
|
1417
|
-
.axi-tabs a:hover,
|
|
1418
|
-
.axi-tabs button:hover { color: var(--axi-text); border-color: var(--axi-rule); }
|
|
1689
|
+
.axi-tabs :where(a:hover, button:hover) { color: var(--axi-text); border-color: var(--axi-rule); }
|
|
1419
1690
|
/* The current tab is filled and blocked - the same treatment a pressed pill
|
|
1420
1691
|
gets, because it is the same idea: this one is on. */
|
|
1421
1692
|
.axi-tabs a[aria-current="page"],
|
|
@@ -1447,13 +1718,13 @@ textarea.axi-input {
|
|
|
1447
1718
|
text-overflow: ellipsis;
|
|
1448
1719
|
white-space: nowrap;
|
|
1449
1720
|
}
|
|
1450
|
-
.axi-tabs .axi-tabs__close {
|
|
1721
|
+
.axi-tabs :where(.axi-tabs__close) {
|
|
1451
1722
|
padding: 0 6px;
|
|
1452
1723
|
font: var(--axi-t-micro);
|
|
1453
1724
|
color: var(--axi-text-faint);
|
|
1454
1725
|
border-color: transparent;
|
|
1455
1726
|
}
|
|
1456
|
-
.axi-tabs .axi-tabs__close:hover { color: var(--axi-text); border-color: transparent; }
|
|
1727
|
+
.axi-tabs :where(.axi-tabs__close:hover) { color: var(--axi-text); border-color: transparent; }
|
|
1457
1728
|
|
|
1458
1729
|
/* ---------- rail ---------- */
|
|
1459
1730
|
/* Vertical navigation, for a set too large for a tab strip to hold. Same
|
|
@@ -1527,7 +1798,7 @@ textarea.axi-input {
|
|
|
1527
1798
|
border-radius: var(--axi-radius-sm);
|
|
1528
1799
|
cursor: pointer;
|
|
1529
1800
|
}
|
|
1530
|
-
.axi-rail__item:hover { color: var(--axi-text); background: var(--axi-surface-raised); }
|
|
1801
|
+
.axi-rail__item:where(:hover) { color: var(--axi-text); background: var(--axi-surface-raised); }
|
|
1531
1802
|
/* The same treatment a pressed pill and a current tab get. Only ever one of
|
|
1532
1803
|
these on screen. Any aria-current value, because the right one depends on
|
|
1533
1804
|
what the rail navigates - `page` for a view, `location` for a place within
|
|
@@ -1590,7 +1861,7 @@ textarea.axi-input {
|
|
|
1590
1861
|
border-radius: var(--axi-radius-sm);
|
|
1591
1862
|
cursor: pointer;
|
|
1592
1863
|
}
|
|
1593
|
-
.axi-rail__subitem:hover { color: var(--axi-text-dim); background: var(--axi-surface-raised); }
|
|
1864
|
+
.axi-rail__subitem:where(:hover) { color: var(--axi-text-dim); background: var(--axi-surface-raised); }
|
|
1594
1865
|
.axi-rail__subitem[aria-current] { color: var(--axi-text); }
|
|
1595
1866
|
/* An annotation on an item, not a measurement of it, so rule 5 draws it
|
|
1596
1867
|
outlined rather than filled. */
|
|
@@ -1738,13 +2009,13 @@ textarea.axi-input {
|
|
|
1738
2009
|
box-shadow: var(--axi-shadow-panel);
|
|
1739
2010
|
}
|
|
1740
2011
|
.axi-menu__pop[hidden] { display: none; }
|
|
1741
|
-
.axi-menu__pop label {
|
|
2012
|
+
.axi-menu__pop :where(label) {
|
|
1742
2013
|
display: flex; gap: 9px; align-items: flex-start;
|
|
1743
2014
|
padding: 7px 8px; border-radius: var(--axi-radius-sm);
|
|
1744
2015
|
font-size: 13px; font-weight: 600; color: var(--axi-text-dim); cursor: pointer;
|
|
1745
2016
|
}
|
|
1746
2017
|
/* A hovered row sinks - the same fill the select's and the picker's rows use. */
|
|
1747
|
-
.axi-menu__pop label:hover { background: var(--axi-well-fill); color: var(--axi-text); }
|
|
2018
|
+
.axi-menu__pop :where(label:hover) { background: var(--axi-well-fill); color: var(--axi-text); }
|
|
1748
2019
|
.axi-menu__pop input { margin: 3px 0 0; }
|
|
1749
2020
|
|
|
1750
2021
|
/* ---------- command palette ---------- */
|
|
@@ -1805,14 +2076,16 @@ textarea.axi-input {
|
|
|
1805
2076
|
}
|
|
1806
2077
|
/* min-height: 0 because the panel is a flex column and a list that cannot
|
|
1807
2078
|
shrink below its content pushes the panel past its own max-height. The
|
|
1808
|
-
scrollbar is hidden
|
|
1809
|
-
|
|
1810
|
-
|
|
2079
|
+
scrollbar is hidden, but not here: that decision is the language's and not
|
|
2080
|
+
this component's, and it now lives once, in src/utilities.css under "the
|
|
2081
|
+
quiet scroll", with this selector in the same rule as every other strip that
|
|
2082
|
+
makes it. The argument that produced it was written here first - at twelve
|
|
2083
|
+
rows behind a fixed cap the bar is a channel carved down the side of a small
|
|
2084
|
+
panel to report a fact the row count in the bar has already given - and it is
|
|
2085
|
+
preserved in full at the rule. */
|
|
1811
2086
|
.axi-palette__list {
|
|
1812
2087
|
flex: 1; min-height: 0; overflow-y: auto; padding: 4px 0;
|
|
1813
|
-
scrollbar-width: none;
|
|
1814
2088
|
}
|
|
1815
|
-
.axi-palette__list::-webkit-scrollbar { width: 0; height: 0; }
|
|
1816
2089
|
/* One .axi-palette__section per group of rows, each opening with a
|
|
1817
2090
|
.axi-palette__group heading. The section is a named part rather than left to
|
|
1818
2091
|
the consumer's wrapper so the rule between groups can be stated as what it
|
|
@@ -1834,7 +2107,7 @@ textarea.axi-input {
|
|
|
1834
2107
|
color: var(--axi-text-dim);
|
|
1835
2108
|
font: var(--axi-t-small); text-align: left; cursor: pointer;
|
|
1836
2109
|
}
|
|
1837
|
-
.axi-palette__row:hover { background: var(--axi-surface-raised); color: var(--axi-text); }
|
|
2110
|
+
.axi-palette__row:where(:hover) { background: var(--axi-surface-raised); color: var(--axi-text); }
|
|
1838
2111
|
.axi-palette__row[data-active] { background: var(--axi-accent); color: var(--axi-accent-ink); }
|
|
1839
2112
|
/* The row's own ink is set above, but a row carries a glyph and a second line
|
|
1840
2113
|
of text that were coloured on their own; on the filled row they all become
|
|
@@ -1866,7 +2139,7 @@ textarea.axi-input {
|
|
|
1866
2139
|
cursor: pointer;
|
|
1867
2140
|
transition: transform .1s, box-shadow .1s, color .1s;
|
|
1868
2141
|
}
|
|
1869
|
-
.axi-palette__trigger:hover {
|
|
2142
|
+
.axi-palette__trigger:where(:hover) {
|
|
1870
2143
|
color: var(--axi-text);
|
|
1871
2144
|
box-shadow: var(--axi-shadow-control);
|
|
1872
2145
|
transform: translate(-2px, -2px);
|
|
@@ -2013,7 +2286,7 @@ textarea.axi-input {
|
|
|
2013
2286
|
color: var(--axi-text-faint);
|
|
2014
2287
|
}
|
|
2015
2288
|
.axi-card__go { margin-left: auto; color: var(--axi-accent); font-weight: 900; text-transform: uppercase; letter-spacing: .05em; }
|
|
2016
|
-
.axi-card:hover .axi-card__go { color: var(--axi-text); }
|
|
2289
|
+
.axi-card:where(:hover) :where(.axi-card__go) { color: var(--axi-text); }
|
|
2017
2290
|
|
|
2018
2291
|
/* ---------- drawer ---------- */
|
|
2019
2292
|
/* A scrim's layer is not a property of the scrim. It is "directly below the
|
|
@@ -2086,7 +2359,7 @@ textarea.axi-input {
|
|
|
2086
2359
|
font-size: 15px; font-weight: 900; cursor: pointer;
|
|
2087
2360
|
transition: transform .1s, box-shadow .1s;
|
|
2088
2361
|
}
|
|
2089
|
-
.axi-drawer__close:hover {
|
|
2362
|
+
.axi-drawer__close:where(:hover) {
|
|
2090
2363
|
background: var(--axi-accent); color: var(--axi-accent-ink);
|
|
2091
2364
|
box-shadow: var(--axi-shadow-control);
|
|
2092
2365
|
transform: translate(-2px, -2px);
|
|
@@ -2228,7 +2501,7 @@ textarea.axi-input {
|
|
|
2228
2501
|
-webkit-app-region: drag;
|
|
2229
2502
|
}
|
|
2230
2503
|
.axi-titlebar__btns { margin-left: auto; display: flex; height: 100%; -webkit-app-region: no-drag; }
|
|
2231
|
-
.axi-titlebar__btns button {
|
|
2504
|
+
.axi-titlebar__btns :where(button) {
|
|
2232
2505
|
width: 44px;
|
|
2233
2506
|
border: 0;
|
|
2234
2507
|
background: transparent;
|
|
@@ -2239,10 +2512,10 @@ textarea.axi-input {
|
|
|
2239
2512
|
/* No lift here. A window button is chrome, not content: it has nothing to
|
|
2240
2513
|
lift off, and 44px of movement in the corner of every screen is exactly
|
|
2241
2514
|
the scattered motion rule 4 rations. It answers on the neutral ramp. */
|
|
2242
|
-
.axi-titlebar__btns button:hover { background: var(--axi-surface); color: var(--axi-text); }
|
|
2515
|
+
.axi-titlebar__btns :where(button:hover) { background: var(--axi-surface); color: var(--axi-text); }
|
|
2243
2516
|
/* Close is the one destructive control in the chrome, and the one place a
|
|
2244
2517
|
status ink belongs in a titlebar. */
|
|
2245
|
-
.axi-titlebar__btns button:last-child:hover { background: var(--axi-danger); color: var(--axi-ink-on-fill); }
|
|
2518
|
+
.axi-titlebar__btns :where(button:last-child:hover) { background: var(--axi-danger); color: var(--axi-ink-on-fill); }
|
|
2246
2519
|
/* The scrolling region under the chrome. */
|
|
2247
2520
|
.axi-window__body { flex: 1; overflow-y: auto; }
|
|
2248
2521
|
|
|
@@ -2261,8 +2534,8 @@ textarea.axi-input {
|
|
|
2261
2534
|
flex-wrap: wrap;
|
|
2262
2535
|
font: var(--axi-t-small);
|
|
2263
2536
|
}
|
|
2264
|
-
.axi-crumbs a { color: var(--axi-text-dim); text-decoration: none; }
|
|
2265
|
-
.axi-crumbs a:hover { color: var(--axi-accent); }
|
|
2537
|
+
.axi-crumbs :where(a) { color: var(--axi-text-dim); text-decoration: none; }
|
|
2538
|
+
.axi-crumbs :where(a:hover) { color: var(--axi-accent); }
|
|
2266
2539
|
/* 7px, matching the prose bullet (src/prose.css) exactly, with the same
|
|
2267
2540
|
--axi-border-hairline outline: rule 7 calls the motif "a 45deg-rotated
|
|
2268
2541
|
outlined square", and a bare fill with no border does not match it. The
|
|
@@ -2296,7 +2569,7 @@ textarea.axi-input {
|
|
|
2296
2569
|
border: var(--axi-border-control) solid var(--axi-ink-line);
|
|
2297
2570
|
border-radius: var(--axi-radius-sm);
|
|
2298
2571
|
}
|
|
2299
|
-
.axi-pages__n:hover { color: var(--axi-text); }
|
|
2572
|
+
.axi-pages__n:where(:hover) { color: var(--axi-text); }
|
|
2300
2573
|
.axi-pages__n[aria-current="page"] {
|
|
2301
2574
|
background: var(--axi-accent);
|
|
2302
2575
|
color: var(--axi-accent-ink);
|
|
@@ -2309,7 +2582,12 @@ textarea.axi-input {
|
|
|
2309
2582
|
@media (max-width: 640px) {
|
|
2310
2583
|
.axi-mast__in { min-height: 0; padding: 10px 16px; gap: 12px; }
|
|
2311
2584
|
.axi-mast .axi-tabs { margin-left: 0; }
|
|
2312
|
-
|
|
2585
|
+
/* Every strip scrolls on a phone, whether or not it asked. This cannot join
|
|
2586
|
+
the shared rule above - a media-query rule and an unconditional one are
|
|
2587
|
+
different rules by construction - so the values are pinned against it by
|
|
2588
|
+
test instead: the arm may declare nothing the modifier does not, and
|
|
2589
|
+
nothing at a different value. */
|
|
2590
|
+
.axi-tabs { width: 100%; flex-shrink: 1; min-width: 0; overflow-x: auto; }
|
|
2313
2591
|
/* The offset blocks eat horizontal room a phone does not have, so the panel
|
|
2314
2592
|
step drops to the control step here rather than to an invented number.
|
|
2315
2593
|
The card's hover has to come down with it - left alone it kept deepening
|
|
@@ -2601,7 +2879,17 @@ textarea.axi-input {
|
|
|
2601
2879
|
resting cell rule (`.axi-table :where(td, tbody th)`, also one class) and
|
|
2602
2880
|
wins on order, while an ink class in utilities.css - one class, later file -
|
|
2603
2881
|
wins over both. */
|
|
2604
|
-
|
|
2882
|
+
/* A DATA FILL SURVIVES A ROW STATE, and the `:not()` sits inside `:where()`
|
|
2883
|
+
so saying so costs no specificity. A matrix cell's band IS its value (rule 9,
|
|
2884
|
+
"the matrix is the bound on not at all") - washing it on the way past the row
|
|
2885
|
+
you want deletes the reading. The row state is not weakened by this: it is
|
|
2886
|
+
drawn by the leading edge below, and by every cell that has nothing to say.
|
|
2887
|
+
Which is the edge-not-fill answer the selection rule below reaches on its own
|
|
2888
|
+
grounds, arriving here a second time.
|
|
2889
|
+
|
|
2890
|
+
Nothing changes for a table with no `data-heat` in it, which is every table
|
|
2891
|
+
in this language but one. */
|
|
2892
|
+
.axi-table :where(tbody tr:hover) :where(td:not([data-heat]), th:not([data-heat])) {
|
|
2605
2893
|
background: var(--axi-surface-raised);
|
|
2606
2894
|
background-attachment: fixed;
|
|
2607
2895
|
color: var(--axi-text);
|
|
@@ -2636,7 +2924,7 @@ textarea.axi-input {
|
|
|
2636
2924
|
.axi-table :where(tbody tr) > :where(td, th):first-child {
|
|
2637
2925
|
border-inline-start: var(--axi-border-control) solid transparent;
|
|
2638
2926
|
}
|
|
2639
|
-
.axi-table tbody tr[aria-current] :is(td, th) {
|
|
2927
|
+
.axi-table tbody tr[aria-current] :is(td, th):where(:not([data-heat])) {
|
|
2640
2928
|
background: var(--axi-surface-raised);
|
|
2641
2929
|
background-attachment: fixed;
|
|
2642
2930
|
color: var(--axi-text);
|
|
@@ -2797,6 +3085,115 @@ textarea.axi-input {
|
|
|
2797
3085
|
}
|
|
2798
3086
|
.axi-table th:first-child .axi-table__sort { justify-content: flex-start; }
|
|
2799
3087
|
|
|
3088
|
+
/* ---------- matrix ---------- */
|
|
3089
|
+
/* Two categorical axes and a quantity where they cross: forty players down the
|
|
3090
|
+
side, sixty five-second buckets across, a count in each cell. Rule 9's
|
|
3091
|
+
"the matrix is the bound on not at all" is the whole argument for this
|
|
3092
|
+
existing at all, and the short form is: the plane is spent, so there is no
|
|
3093
|
+
third dimension left to give the quantity a LENGTH, and both axes are
|
|
3094
|
+
already sorted by something the reader needs so neither can be re-sorted by
|
|
3095
|
+
the value. Intensity is what is left, and it is admissible here because the
|
|
3096
|
+
cell also prints its number - the digit is the legible copy rule 9 demands,
|
|
3097
|
+
and the band is only what lets the eye find the shape without reading two
|
|
3098
|
+
thousand figures one at a time. A matrix cell with nothing written in it is
|
|
3099
|
+
a heatmap and rule 9 refuses it.
|
|
3100
|
+
|
|
3101
|
+
This is a MODIFIER and not a component, and that is the finding rather than
|
|
3102
|
+
a convenience. Everything structural a matrix field needs, this table
|
|
3103
|
+
already had: --sticky for the ruler that stays, --pinned for the names that
|
|
3104
|
+
stay, --dense for sixty columns' padding, --fixed plus a <colgroup> for
|
|
3105
|
+
proportional cells, and .axi-table__scroll for the frame they move inside.
|
|
3106
|
+
The consumer measured against here had hand-written all five, and had got
|
|
3107
|
+
--sticky wrong in exactly the way that modifier's comment warns about: its
|
|
3108
|
+
ruler was --axi-surface-raised, so under glass the rows slid through it.
|
|
3109
|
+
The only thing genuinely missing was the quantity.
|
|
3110
|
+
|
|
3111
|
+
What the modifier changes is the CELL, which stops being a number in a
|
|
3112
|
+
column and becomes a patch in a field: centred rather than right-aligned,
|
|
3113
|
+
because a band is read by position and not by its digits lining up, and
|
|
3114
|
+
unpadded because the padding is the field. The name column keeps the base
|
|
3115
|
+
table's left alignment and its gutter - it is still a name. */
|
|
3116
|
+
.axi-table--matrix { border-collapse: separate; border-spacing: 0; }
|
|
3117
|
+
.axi-table--matrix thead th { padding: 0 0 6px; text-align: center; }
|
|
3118
|
+
.axi-table--matrix :is(td, tbody th) {
|
|
3119
|
+
padding: 0;
|
|
3120
|
+
text-align: center;
|
|
3121
|
+
height: var(--axi-matrix-cell, 24px);
|
|
3122
|
+
}
|
|
3123
|
+
/* `min-width` and not `width`: under the default auto layout this is the floor
|
|
3124
|
+
a one-digit column cannot fall below, and under --fixed a <colgroup> takes
|
|
3125
|
+
over entirely (fixed layout ignores both min and max - see --fixed). */
|
|
3126
|
+
.axi-table--matrix :is(td, tbody th):not(:first-child) {
|
|
3127
|
+
min-width: var(--axi-matrix-cell, 24px);
|
|
3128
|
+
}
|
|
3129
|
+
.axi-table--matrix :is(td, tbody th):first-child {
|
|
3130
|
+
text-align: left;
|
|
3131
|
+
padding: 0 10px 0 0;
|
|
3132
|
+
}
|
|
3133
|
+
/* The four bands. Each is a color-mix() of the accent into the surface's flat
|
|
3134
|
+
companion, which makes every one of them a computed OPAQUE colour - the
|
|
3135
|
+
accent at 18% alpha over the field would be rule 2's faded ink, and it also
|
|
3136
|
+
fails on its own terms, because the cells a reader is scanning for are the
|
|
3137
|
+
quiet ones and an alpha ramp is where those disappear.
|
|
3138
|
+
|
|
3139
|
+
--axi-surface-paint and not --axi-surface, and this is the trap: a surface
|
|
3140
|
+
token is allowed to hold a gradient (rule 1's one relief, which is how a
|
|
3141
|
+
glass theme exists), color-mix() takes colours and nothing else, and an
|
|
3142
|
+
invalid color-mix() is dropped at computed-value time. Spell it --axi-surface
|
|
3143
|
+
and the bands do not fade - they vanish, silently, in whichever theme paints
|
|
3144
|
+
a gradient. That is measured, not hypothetical: it is why
|
|
3145
|
+
--axi-surface-paint exists (see tokens.css).
|
|
3146
|
+
|
|
3147
|
+
Four steps and not a continuous ramp, because a band is also where the digit
|
|
3148
|
+
has to change ink, and a step is the only thing a flip can happen ON. The
|
|
3149
|
+
top two carry --axi-accent-ink for the same reason .axi-btn--primary does:
|
|
3150
|
+
past roughly half strength the field is the accent and the text on it is the
|
|
3151
|
+
accent's companion, not the text ramp. */
|
|
3152
|
+
.axi-table--matrix :is(td, tbody th)[data-heat='1'] {
|
|
3153
|
+
background-color: color-mix(in srgb, var(--axi-accent) 18%, var(--axi-surface-paint));
|
|
3154
|
+
}
|
|
3155
|
+
.axi-table--matrix :is(td, tbody th)[data-heat='2'] {
|
|
3156
|
+
background-color: color-mix(in srgb, var(--axi-accent) 42%, var(--axi-surface-paint));
|
|
3157
|
+
}
|
|
3158
|
+
.axi-table--matrix :is(td, tbody th)[data-heat='3'] {
|
|
3159
|
+
background-color: color-mix(in srgb, var(--axi-accent) 70%, var(--axi-surface-paint));
|
|
3160
|
+
color: var(--axi-accent-ink);
|
|
3161
|
+
}
|
|
3162
|
+
.axi-table--matrix :is(td, tbody th)[data-heat='4'] {
|
|
3163
|
+
background-color: color-mix(in srgb, var(--axi-accent) 100%, var(--axi-surface-paint));
|
|
3164
|
+
color: var(--axi-accent-ink);
|
|
3165
|
+
}
|
|
3166
|
+
/* A matrix whose columns are a TIMELINE rather than a list of categories, which
|
|
3167
|
+
changes two things and only two.
|
|
3168
|
+
|
|
3169
|
+
The column labels move left. A label over a category names the column and
|
|
3170
|
+
belongs centred over it; a label on a ruler names a MOMENT, and centring it
|
|
3171
|
+
puts the text half a cell to the right of the instant it is pointing at.
|
|
3172
|
+
|
|
3173
|
+
And the division gets a line. `[data-tick]` marks the columns that carry a
|
|
3174
|
+
label - every thirtieth second, not every bucket, because at a five-second
|
|
3175
|
+
resolution a five-minute fight is sixty columns and a timestamp over each is
|
|
3176
|
+
unreadable at the width a cell allows. The line is the RULE, at the hairline
|
|
3177
|
+
step: it is the same line that parts the rows, continued down the field,
|
|
3178
|
+
which is the whole reason it is not drawn on every column. Sixty ruled
|
|
3179
|
+
columns is a spreadsheet; the bands are meant to be the figure. */
|
|
3180
|
+
.axi-table--ruler thead th { text-align: left; }
|
|
3181
|
+
.axi-table--ruler :is(thead th, td, tbody th)[data-tick] {
|
|
3182
|
+
border-left: var(--axi-border-hairline) solid var(--axi-rule);
|
|
3183
|
+
}
|
|
3184
|
+
/* A change of category down the rows - a subgroup, a team, a date. Heavier than
|
|
3185
|
+
the hairline that parts two rows of one group, and heavier in INK rather than
|
|
3186
|
+
in weight: the same line, drawn darker. Going up a form step instead would
|
|
3187
|
+
put a control-weight line inside running content, which is rule 8's grid of
|
|
3188
|
+
boxes, and the boundary would then compete with the head's own lid.
|
|
3189
|
+
|
|
3190
|
+
An attribute and not a class, per rule 13, and on the row because that is
|
|
3191
|
+
what starts: the consumer compares one row's category with the previous
|
|
3192
|
+
row's, which is a fact only it can know. */
|
|
3193
|
+
.axi-table tbody tr[data-group-start] > :is(td, th) {
|
|
3194
|
+
border-top: var(--axi-border-hairline) solid var(--axi-ink-line);
|
|
3195
|
+
}
|
|
3196
|
+
|
|
2800
3197
|
/* ---------- meter ---------- */
|
|
2801
3198
|
/* Rule 9: a proportion is a length. The track is the ground, the fill is the
|
|
2802
3199
|
value, and the fill is one ink at full strength - a tinted or faded bar is
|
|
@@ -2846,6 +3243,61 @@ textarea.axi-input {
|
|
|
2846
3243
|
font-variant-numeric: tabular-nums;
|
|
2847
3244
|
}
|
|
2848
3245
|
|
|
3246
|
+
/* ---------- readout ---------- */
|
|
3247
|
+
/* A short run of labelled readings: logs seen, uploaded, failed; the session's
|
|
3248
|
+
start and its length; a setting and the switch that sets it. The shape of
|
|
3249
|
+
every status card in a dashboard's side column, and the one the consumer
|
|
3250
|
+
that had four of them built by hand each time.
|
|
3251
|
+
|
|
3252
|
+
Rule 8 settles how it is drawn. The eye runs down the value column - that is
|
|
3253
|
+
the whole point of stacking the readings - so it is a table's interior:
|
|
3254
|
+
rows parted by the rule at the hairline weight, no outline and no block on
|
|
3255
|
+
any row, nothing raised inside the panel that is already the raised thing.
|
|
3256
|
+
It is not a table element because it is two cells wide, has no head, and
|
|
3257
|
+
the reading in the second cell is as often a control as a figure; a <dl> is
|
|
3258
|
+
what the document structure calls for and the classes sit on it.
|
|
3259
|
+
|
|
3260
|
+
The key is the meter list's name and the value is the meter list's value,
|
|
3261
|
+
restated here rather than aliased because the two lists are laid out
|
|
3262
|
+
differently and rule-8's test - is there a column? - is the same for both.
|
|
3263
|
+
|
|
3264
|
+
Row padding is a knob at two fallbacks, like the eyebrow's gap: 7px at
|
|
3265
|
+
panel density, and 3px inside a tile, where a hairline already holds the
|
|
3266
|
+
rows apart and 3px is still a clear gap at this type size. */
|
|
3267
|
+
.axi-readout {
|
|
3268
|
+
display: flex;
|
|
3269
|
+
flex-direction: column;
|
|
3270
|
+
margin: 0;
|
|
3271
|
+
}
|
|
3272
|
+
.axi-readout__row {
|
|
3273
|
+
display: flex;
|
|
3274
|
+
align-items: center;
|
|
3275
|
+
justify-content: space-between;
|
|
3276
|
+
gap: 10px;
|
|
3277
|
+
padding: var(--axi-readout-pad, 7px) 0;
|
|
3278
|
+
}
|
|
3279
|
+
.axi-readout__row + .axi-readout__row {
|
|
3280
|
+
border-top: var(--axi-border-hairline) solid var(--axi-rule);
|
|
3281
|
+
}
|
|
3282
|
+
.axi-readout__k {
|
|
3283
|
+
min-width: 0;
|
|
3284
|
+
margin: 0;
|
|
3285
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
3286
|
+
font: var(--axi-t-small);
|
|
3287
|
+
font-weight: 700;
|
|
3288
|
+
color: var(--axi-text-dim);
|
|
3289
|
+
}
|
|
3290
|
+
.axi-readout__v {
|
|
3291
|
+
flex: none;
|
|
3292
|
+
margin: 0;
|
|
3293
|
+
text-align: right;
|
|
3294
|
+
font: var(--axi-t-micro);
|
|
3295
|
+
letter-spacing: var(--axi-ls-micro);
|
|
3296
|
+
color: var(--axi-text);
|
|
3297
|
+
font-variant-numeric: tabular-nums;
|
|
3298
|
+
}
|
|
3299
|
+
.axi-panel--tile .axi-readout__row { padding-block: var(--axi-readout-pad, 3px); }
|
|
3300
|
+
|
|
2849
3301
|
/* ---------- bars ---------- */
|
|
2850
3302
|
/* The same rule stood on end. The baseline is drawn at the control weight
|
|
2851
3303
|
because it is an axis - the one line in a chart that is structure rather
|
|
@@ -2975,6 +3427,163 @@ textarea.axi-input {
|
|
|
2975
3427
|
text-transform: uppercase;
|
|
2976
3428
|
color: var(--axi-text-dim);
|
|
2977
3429
|
}
|
|
3430
|
+
/* A key you can press, to isolate the series it names. The state is the
|
|
3431
|
+
attribute rule 13 asks for: `aria-pressed="true"` on the key that is
|
|
3432
|
+
isolated, and nothing at all on the others - which of them recede is the
|
|
3433
|
+
legend's to work out, from whether any key is pressed, rather than a second
|
|
3434
|
+
attribute the consumer has to compute and keep in step.
|
|
3435
|
+
|
|
3436
|
+
Receding is a step down the neutral ramp and not an opacity, which is rule
|
|
3437
|
+
4's "nothing fades" reaching the legend: the consumer this was written for
|
|
3438
|
+
had the rest of the keys at `opacity: .3` and they were unreadable, which
|
|
3439
|
+
is the one thing a legend must not be. A receded key brightens back to
|
|
3440
|
+
plain under the cursor so it can be found again - the hover is wrapped so it
|
|
3441
|
+
costs nothing against an ink, and the receded rule steps aside for it.
|
|
3442
|
+
|
|
3443
|
+
The button reset and the hover are :where()-wrapped for the reason the ink
|
|
3444
|
+
layer gives: an inked key keeps its ink. The pressed and receded colours are
|
|
3445
|
+
the state's own meaning and stay at full weight. */
|
|
3446
|
+
.axi-legend__key:where(button) {
|
|
3447
|
+
padding: 0;
|
|
3448
|
+
background: none;
|
|
3449
|
+
border: 0;
|
|
3450
|
+
cursor: pointer;
|
|
3451
|
+
transition: color .1s;
|
|
3452
|
+
}
|
|
3453
|
+
.axi-legend__key:where(button:hover) { color: var(--axi-text); }
|
|
3454
|
+
.axi-legend__key[aria-pressed="true"] { color: var(--axi-text); }
|
|
3455
|
+
.axi-legend:has([aria-pressed="true"]) .axi-legend__key:where(:not([aria-pressed="true"], :hover)) {
|
|
3456
|
+
color: var(--axi-text-faint);
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
/* ---------- library charts ---------- */
|
|
3460
|
+
/* The language draws its own bars, plot and ticks above. An app that needs a
|
|
3461
|
+
stacked area, a brush or a pie reaches for a charting library instead, and
|
|
3462
|
+
the library arrives with its own furniture: a dashed grey grid, a #666 axis,
|
|
3463
|
+
a slate tooltip with a half-rem corner. Every series it draws is the
|
|
3464
|
+
consumer's data and keeps its own colour (rule 10). Everything drawn AROUND
|
|
3465
|
+
the series is furniture, and furniture is the language's.
|
|
3466
|
+
|
|
3467
|
+
This is the binding for recharts, which is the library the suite uses. It
|
|
3468
|
+
is scoped to `.axi-chart` so a chart opts in by wrapping, and it reaches the
|
|
3469
|
+
library's elements by the class names recharts puts on them. recharts spreads
|
|
3470
|
+
stroke and fill onto its SVG as presentation attributes, which a stylesheet
|
|
3471
|
+
outranks, so nothing here needs to be passed into the library - and nothing
|
|
3472
|
+
here is written with !important. A consumer that hands the library an inline
|
|
3473
|
+
`contentStyle` has written a second design for the tooltip, and the answer
|
|
3474
|
+
is to stop writing it, not for the language to shout over it: the tooltip is
|
|
3475
|
+
`.axi-tooltip--flow` as custom content, drawn once, like every other tooltip.
|
|
3476
|
+
|
|
3477
|
+
The grammar is the one every component here already follows. Furniture is
|
|
3478
|
+
drawn in the neutral ramp and recedes: the grid at --axi-grid, a step below
|
|
3479
|
+
the rule (see tokens.css); the axis as a rule at the hairline weight, the
|
|
3480
|
+
same line the table draws between rows; tick labels in the faint ink with
|
|
3481
|
+
tabular figures, so a y-axis whose digits change width holds still. Every
|
|
3482
|
+
small object standing on a series - a point, a bar, a slice - takes the ink
|
|
3483
|
+
edge rule 3 gives every other small object, at the hairline because it is a
|
|
3484
|
+
mark and not a raised surface, and keeps its fill, because the fill is which
|
|
3485
|
+
series it belongs to. The hover band is the raised step's -paint companion
|
|
3486
|
+
(a `fill` cannot be a gradient) rather than a white wash, which is rule 2.
|
|
3487
|
+
|
|
3488
|
+
The brush is the one place an accent is spent. Its two travellers are the
|
|
3489
|
+
only thing in any chart you can grab, and they are small: a target you have
|
|
3490
|
+
to find before you can use it is what the accent is for, unlike the hover
|
|
3491
|
+
band, which is already under the pointer. The slide between them is the
|
|
3492
|
+
surface as one paint, the trough behind them a well. One accent in the plot,
|
|
3493
|
+
on the one control.
|
|
3494
|
+
|
|
3495
|
+
Solid, never dashed. A dash is decoration, and fifteen charts of dashes is a
|
|
3496
|
+
texture rather than a scale; the reference line - a threshold, a claim - is
|
|
3497
|
+
solid too, because it is one of the few lines in a chart meant to be read. */
|
|
3498
|
+
.axi-chart {
|
|
3499
|
+
/* Every figure in a chart is a label of a number, so the whole scope reads
|
|
3500
|
+
tabular. Nothing else is declared on the scope: a chart's size belongs to
|
|
3501
|
+
the library's container and the consumer's layout. */
|
|
3502
|
+
font-variant-numeric: tabular-nums;
|
|
3503
|
+
}
|
|
3504
|
+
.axi-chart .recharts-cartesian-grid line {
|
|
3505
|
+
stroke: var(--axi-grid);
|
|
3506
|
+
stroke-dasharray: none;
|
|
3507
|
+
stroke-width: 1;
|
|
3508
|
+
}
|
|
3509
|
+
/* Stripes are a second background inside a chart that already has one. */
|
|
3510
|
+
.axi-chart .recharts-cartesian-grid-bg,
|
|
3511
|
+
.axi-chart .recharts-cartesian-gridstripes-horizontal,
|
|
3512
|
+
.axi-chart .recharts-cartesian-gridstripes-vertical { display: none; }
|
|
3513
|
+
|
|
3514
|
+
.axi-chart .recharts-cartesian-axis-line {
|
|
3515
|
+
stroke: var(--axi-rule);
|
|
3516
|
+
stroke-width: var(--axi-border-hairline);
|
|
3517
|
+
}
|
|
3518
|
+
.axi-chart .recharts-cartesian-axis-tick-line { stroke: var(--axi-rule); }
|
|
3519
|
+
.axi-chart .recharts-cartesian-axis-tick-value { fill: var(--axi-text-faint); }
|
|
3520
|
+
.axi-chart .recharts-label,
|
|
3521
|
+
.axi-chart .recharts-legend-item-text { fill: var(--axi-text-dim); color: var(--axi-text-dim); }
|
|
3522
|
+
/* The legend is a key, so it reads as labels rather than as chips - the same
|
|
3523
|
+
type .axi-legend__key sets. */
|
|
3524
|
+
.axi-chart .recharts-legend-wrapper {
|
|
3525
|
+
font: var(--axi-t-micro);
|
|
3526
|
+
letter-spacing: var(--axi-ls-micro);
|
|
3527
|
+
text-transform: uppercase;
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3530
|
+
/* The hover band. A fill change rather than a wash: the next surface up, as
|
|
3531
|
+
one paint, edged with the rule at the hairline like every line here. */
|
|
3532
|
+
.axi-chart .recharts-tooltip-cursor {
|
|
3533
|
+
fill: var(--axi-surface-raised-paint);
|
|
3534
|
+
fill-opacity: 1;
|
|
3535
|
+
stroke: var(--axi-rule);
|
|
3536
|
+
stroke-width: var(--axi-border-hairline);
|
|
3537
|
+
}
|
|
3538
|
+
/* A line chart's cursor is a crosshair, not a band, so it stays a line. */
|
|
3539
|
+
.axi-chart .recharts-curve.recharts-tooltip-cursor {
|
|
3540
|
+
fill: none;
|
|
3541
|
+
stroke: var(--axi-rule);
|
|
3542
|
+
stroke-dasharray: none;
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3545
|
+
/* Small objects standing on a series keep their fill and take the ink edge. */
|
|
3546
|
+
.axi-chart .recharts-line-dot,
|
|
3547
|
+
.axi-chart .recharts-scatter-symbol > path,
|
|
3548
|
+
.axi-chart .recharts-pie-sector > path,
|
|
3549
|
+
.axi-chart .recharts-bar-rectangle > path {
|
|
3550
|
+
stroke: var(--axi-ink-line);
|
|
3551
|
+
stroke-width: var(--axi-border-hairline);
|
|
3552
|
+
}
|
|
3553
|
+
.axi-chart .recharts-active-dot > * {
|
|
3554
|
+
stroke: var(--axi-ink-line);
|
|
3555
|
+
stroke-width: var(--axi-border-control);
|
|
3556
|
+
}
|
|
3557
|
+
.axi-chart .recharts-bar-background-rectangle { fill: var(--axi-well-fill); }
|
|
3558
|
+
|
|
3559
|
+
.axi-chart .recharts-reference-line-line {
|
|
3560
|
+
stroke: var(--axi-text-faint);
|
|
3561
|
+
stroke-width: var(--axi-border-hairline);
|
|
3562
|
+
stroke-dasharray: none;
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
/* The brush: a well, a slide, two travellers, and the preview curve dropped to
|
|
3566
|
+
furniture weight because it is context rather than data you read off. */
|
|
3567
|
+
.axi-chart .recharts-brush > rect:first-of-type {
|
|
3568
|
+
fill: var(--axi-well-fill);
|
|
3569
|
+
stroke: var(--axi-well-line);
|
|
3570
|
+
stroke-width: var(--axi-border-control);
|
|
3571
|
+
}
|
|
3572
|
+
.axi-chart .recharts-brush-slide {
|
|
3573
|
+
fill: var(--axi-surface-paint);
|
|
3574
|
+
fill-opacity: 1;
|
|
3575
|
+
}
|
|
3576
|
+
.axi-chart .recharts-brush-traveller > rect {
|
|
3577
|
+
fill: var(--axi-accent);
|
|
3578
|
+
stroke: var(--axi-ink-line);
|
|
3579
|
+
stroke-width: var(--axi-border-hairline);
|
|
3580
|
+
}
|
|
3581
|
+
.axi-chart .recharts-brush-traveller > line { stroke: var(--axi-accent-ink); }
|
|
3582
|
+
.axi-chart .recharts-brush-texts { fill: var(--axi-text-faint); }
|
|
3583
|
+
.axi-chart .recharts-brush .recharts-curve {
|
|
3584
|
+
stroke: var(--axi-text-faint);
|
|
3585
|
+
fill: none;
|
|
3586
|
+
}
|
|
2978
3587
|
|
|
2979
3588
|
/* --- feedback.css --- */
|
|
2980
3589
|
/* axi design language - feedback.
|
|
@@ -3147,8 +3756,8 @@ textarea.axi-input {
|
|
|
3147
3756
|
|
|
3148
3757
|
/* --- utilities.css --- */
|
|
3149
3758
|
/* axi design language - utilities.
|
|
3150
|
-
Two families of single-purpose class and one
|
|
3151
|
-
the cascade
|
|
3759
|
+
Two families of single-purpose class, one state and one refusal, and the file
|
|
3760
|
+
is last in the cascade
|
|
3152
3761
|
because that is the whole point of it: a utility has to be able to overrule
|
|
3153
3762
|
the component it sits inside, and it is not allowed to buy that with
|
|
3154
3763
|
!important or with a padded selector.
|
|
@@ -3251,6 +3860,7 @@ textarea.axi-input {
|
|
|
3251
3860
|
.axi-chip--action:is(:disabled, [aria-disabled="true"]),
|
|
3252
3861
|
.axi-drawer__close:is(:disabled, [aria-disabled="true"]),
|
|
3253
3862
|
.axi-input:is(:disabled, [aria-disabled="true"]),
|
|
3863
|
+
.axi-legend__key:where(button):is(:disabled, [aria-disabled="true"]),
|
|
3254
3864
|
.axi-link:is(:disabled, [aria-disabled="true"]),
|
|
3255
3865
|
.axi-palette__row:is(:disabled, [aria-disabled="true"]),
|
|
3256
3866
|
.axi-palette__trigger:is(:disabled, [aria-disabled="true"]),
|
|
@@ -3263,11 +3873,65 @@ textarea.axi-input {
|
|
|
3263
3873
|
.axi-select:is(:disabled, [aria-disabled="true"]),
|
|
3264
3874
|
.axi-switch:is(:disabled, [aria-disabled="true"]),
|
|
3265
3875
|
.axi-table__sort:is(:disabled, [aria-disabled="true"]),
|
|
3266
|
-
.axi-menu__pop label:is(:disabled, [aria-disabled="true"]),
|
|
3267
|
-
.axi-prose a:is(:disabled, [aria-disabled="true"]),
|
|
3268
|
-
.axi-tabs a:is(:disabled, [aria-disabled="true"]),
|
|
3269
|
-
.axi-
|
|
3270
|
-
.axi-titlebar__btns button:is(:disabled, [aria-disabled="true"]) {
|
|
3876
|
+
.axi-menu__pop :where(label):is(:disabled, [aria-disabled="true"]),
|
|
3877
|
+
.axi-prose :where(a):is(:disabled, [aria-disabled="true"]),
|
|
3878
|
+
.axi-tabs :where(a, button):is(:disabled, [aria-disabled="true"]),
|
|
3879
|
+
.axi-titlebar__btns :where(button):is(:disabled, [aria-disabled="true"]) {
|
|
3271
3880
|
opacity: .5;
|
|
3272
3881
|
cursor: not-allowed;
|
|
3273
3882
|
}
|
|
3883
|
+
|
|
3884
|
+
/* ---------- the quiet scroll ---------- */
|
|
3885
|
+
/* A scrollbar down the side of a narrow strip is a channel, not information.
|
|
3886
|
+
|
|
3887
|
+
The language had already decided this and said it in exactly one place, on
|
|
3888
|
+
.axi-palette__list, with the argument written inline: at twelve rows behind
|
|
3889
|
+
a fixed cap the bar reports a fact the row count in the bar has already
|
|
3890
|
+
given. Said only there, it was the language's only word for the decision -
|
|
3891
|
+
and the consumer that needed the same thing for its own strips could not
|
|
3892
|
+
reach it, so it wrote the rule again. Five times, in its own stylesheet, for
|
|
3893
|
+
two rails and a picker slot, twice with a `*` descendant arm because the
|
|
3894
|
+
element that actually scrolls sits one level inside a component it does not
|
|
3895
|
+
control. That is the third instance of the pattern in docs/RULES.md ("A style
|
|
3896
|
+
only reachable through a layer will be re-invented"), and the remedy is the
|
|
3897
|
+
same one: give the decision a name a consumer can spend, and put every
|
|
3898
|
+
spelling of it in ONE rule.
|
|
3899
|
+
|
|
3900
|
+
So: one rule. The class is what a consumer says about its own scroller; the
|
|
3901
|
+
four component parts beside it are the strips the language ships. Two rules
|
|
3902
|
+
that agree today are two rules that disagree after the next edit.
|
|
3903
|
+
|
|
3904
|
+
This is a refusal rather than a utility - it says what NOT to draw - which is
|
|
3905
|
+
why it is here rather than beside each component even though nothing needs
|
|
3906
|
+
the last-in-cascade position to win. One fact about parts living in three
|
|
3907
|
+
different source files has one home, the same as the dead state above.
|
|
3908
|
+
|
|
3909
|
+
Why these four and not everything that scrolls. The strip is the test: a
|
|
3910
|
+
rail is 208px wide and a palette list sits in a small floating panel, so the
|
|
3911
|
+
bar costs a measurable share of the width AND draws a second vertical line
|
|
3912
|
+
beside the object's own edge. A table's horizontal bar is the opposite case
|
|
3913
|
+
and .axi-table__scroll keeps it on purpose (see src/data.css) - there the bar
|
|
3914
|
+
is the only thing telling you a column is off-screen. Nothing here reaches a
|
|
3915
|
+
descendant: `*` would take the table's bar away the moment someone put one
|
|
3916
|
+
in a rail.
|
|
3917
|
+
|
|
3918
|
+
`.axi-rail` and `.axi-rail__nav` both, because either can be the scroller.
|
|
3919
|
+
The rail declares `overflow-y: auto` and is the scroller by default, but a
|
|
3920
|
+
rail with something pinned below its list - a "back" action at the floor -
|
|
3921
|
+
scrolls the nav instead and leaves the rail still. Both are the same strip
|
|
3922
|
+
and the decision cannot depend on which one the consumer chose. */
|
|
3923
|
+
.axi-scroll-quiet,
|
|
3924
|
+
.axi-palette__list,
|
|
3925
|
+
.axi-rail,
|
|
3926
|
+
.axi-rail__nav,
|
|
3927
|
+
.axi-split__nav {
|
|
3928
|
+
scrollbar-width: none;
|
|
3929
|
+
}
|
|
3930
|
+
.axi-scroll-quiet::-webkit-scrollbar,
|
|
3931
|
+
.axi-palette__list::-webkit-scrollbar,
|
|
3932
|
+
.axi-rail::-webkit-scrollbar,
|
|
3933
|
+
.axi-rail__nav::-webkit-scrollbar,
|
|
3934
|
+
.axi-split__nav::-webkit-scrollbar {
|
|
3935
|
+
width: 0;
|
|
3936
|
+
height: 0;
|
|
3937
|
+
}
|