@axiapps/axi-design 1.17.0 → 1.19.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 +212 -2
- package/dist/themes/glass.css +1 -0
- package/package.json +1 -1
- package/src/primitives.css +21 -0
- package/src/shells.css +177 -2
- package/src/tokens.css +14 -0
package/README.md
CHANGED
|
@@ -149,6 +149,8 @@ not "what does the system look like". Everything else is
|
|
|
149
149
|
| `--axi-stack-gap` | gap between `.axi-stack` children | `12px` | `<div class="axi-stack" style="--axi-stack-gap: 20px">` |
|
|
150
150
|
| `--axi-panel-pad` | `.axi-panel`'s own padding | `26px` | `<div class="axi-panel" style="--axi-panel-pad: 14px">` |
|
|
151
151
|
| `--axi-page-pad` | `.axi-page`'s horizontal gutter | `var(--axi-gutter)` | `<div class="axi-page axi-page--narrow" style="--axi-page-pad: 0">` |
|
|
152
|
+
| `--axi-palette-w` | the widest an `.axi-palette__panel` gets (it fills the room below that) | `560px` | `<div class="axi-palette__panel" style="--axi-palette-w: 720px">` |
|
|
153
|
+
| `--axi-palette-top` | how far down the screen an `.axi-palette` opens | `12vh` | `<div class="axi-scrim axi-palette" style="--axi-palette-top: 6vh">` |
|
|
152
154
|
| `--axi-menu-width` | width of `.axi-menu__pop` | `310px` | `<div class="axi-menu__pop" style="--axi-menu-width: 380px">` |
|
|
153
155
|
| `--axi-drawer-width` | width of `.axi-drawer` (capped at `100vw`) | `560px` | `<aside class="axi-drawer" style="--axi-drawer-width: 720px">` |
|
|
154
156
|
| `--axi-series` | the ink a meter fill, bar, plot line or `.axi-diamond--series` is drawn in | `var(--axi-accent)` | `<span class="axi-meter__fill" style="--axi-series: var(--axi-ok)">` |
|
package/dist/axi.css
CHANGED
|
@@ -32,6 +32,20 @@
|
|
|
32
32
|
--axi-ground-image: none;
|
|
33
33
|
--axi-surface: #222731; /* a panel raised off the page */
|
|
34
34
|
--axi-surface-raised: #2b313d; /* a chip or popover raised off a panel */
|
|
35
|
+
/* The fill for a surface that FLOATS over content which scrolls behind it: a
|
|
36
|
+
command palette, a menu popover, a modal. Holds --axi-surface, because for
|
|
37
|
+
a theme whose panels are opaque a floating panel is just a panel and there
|
|
38
|
+
is nothing to decide.
|
|
39
|
+
|
|
40
|
+
It exists for the theme whose panels are not. A translucent pane is legible
|
|
41
|
+
standing still on the page and stops being legible the moment a table
|
|
42
|
+
scrolls under it - the text it is carrying now has text behind it. The blur
|
|
43
|
+
that was supposed to rescue that is not something a language can promise:
|
|
44
|
+
backdrop-filter is unavailable or switched off on whole platforms, and
|
|
45
|
+
where it is missing the alpha is the only thing left. So the float gets its
|
|
46
|
+
own token, and a theme that goes translucent restates it near-opaque
|
|
47
|
+
without giving up the tint on everything that merely sits still. */
|
|
48
|
+
--axi-surface-float: var(--axi-surface);
|
|
35
49
|
--axi-ink-line: #0c0e12; /* the outline every raised element is drawn with */
|
|
36
50
|
/* The tone a shape is made of when it is made of the line ink rather than
|
|
37
51
|
outlined in it - the tooltip, the titlebar strip, the switch's slug. Holds
|
|
@@ -501,6 +515,27 @@ a { color: inherit; }
|
|
|
501
515
|
color: var(--axi-accent); font-weight: 900; pointer-events: none;
|
|
502
516
|
}
|
|
503
517
|
|
|
518
|
+
/* ---------- keyboard key ---------- */
|
|
519
|
+
/* A key on the keyboard, named in the interface: the Esc that closes a
|
|
520
|
+
palette, the Ctrl K that opens it. Drawn as a small control rather than as a
|
|
521
|
+
label, because what it depicts is a thing you press - so it takes the
|
|
522
|
+
control edge and the control radius, and every consumer that has a shortcut
|
|
523
|
+
to show stops having to invent a box for it.
|
|
524
|
+
|
|
525
|
+
It sits on --axi-surface, not --axi-ground: a key is raised out of whatever
|
|
526
|
+
it is printed on, and the fields and bars these appear in are already the
|
|
527
|
+
recessed thing. */
|
|
528
|
+
.axi-kbd {
|
|
529
|
+
display: inline-block;
|
|
530
|
+
padding: 2px 5px;
|
|
531
|
+
background: var(--axi-surface);
|
|
532
|
+
border: var(--axi-border-control) solid var(--axi-ink-line);
|
|
533
|
+
border-radius: var(--axi-radius-sm);
|
|
534
|
+
color: var(--axi-text-dim);
|
|
535
|
+
font: 700 10px/1 var(--axi-sans);
|
|
536
|
+
white-space: nowrap;
|
|
537
|
+
}
|
|
538
|
+
|
|
504
539
|
/* ---------- select ---------- */
|
|
505
540
|
/* The closed box is ours everywhere: strip the native control and draw the
|
|
506
541
|
caret, so a select sits alongside the other controls as just another
|
|
@@ -887,6 +922,29 @@ textarea.axi-input {
|
|
|
887
922
|
padding-inline: var(--axi-gutter);
|
|
888
923
|
display: flex; align-items: center; gap: 22px; min-height: 66px; flex-wrap: wrap;
|
|
889
924
|
}
|
|
925
|
+
/* Above --axi-page the row stops growing: the cap is on this box, not on the
|
|
926
|
+
window, so from 1280px up the masthead has exactly one width to fit in and
|
|
927
|
+
wrapping there is not a fallback, it is a permanent second line - one tab
|
|
928
|
+
too many and the last item in source order drops, on a 4K display as surely
|
|
929
|
+
as on a laptop. So the wide row does not wrap; it takes the width out of the
|
|
930
|
+
search field instead (below), and the strip scrolls only if that runs out.
|
|
931
|
+
Under the cap the window really is too narrow and wrapping is the honest
|
|
932
|
+
answer, which is why this is a floor and not a blanket `nowrap`. */
|
|
933
|
+
@media (min-width: 1280px) {
|
|
934
|
+
.axi-mast__in { flex-wrap: nowrap; }
|
|
935
|
+
}
|
|
936
|
+
/* Nothing in the row gives up width unless it is named below. */
|
|
937
|
+
.axi-mast__in > * { flex-shrink: 0; }
|
|
938
|
+
/* The field is the one control here that is still entirely itself 70px
|
|
939
|
+
narrower - a placeholder that elides costs nothing, where a clipped tab
|
|
940
|
+
costs a destination. Flexbox shrinks proportionally, not in order, and a
|
|
941
|
+
merely-larger factor still took a couple of pixels off the last tab; the
|
|
942
|
+
number is this big to make that share round to nothing, so the effect is
|
|
943
|
+
the ordering CSS has no other way to say. Once the field is at its floor -
|
|
944
|
+
still wide enough to read as a search box rather than a smudge - whatever
|
|
945
|
+
is left over goes to the strip, which scrolls. */
|
|
946
|
+
.axi-mast .axi-search,
|
|
947
|
+
.axi-mast .docs-search { flex-shrink: 1000; min-width: 200px; }
|
|
890
948
|
.axi-brand { display: flex; align-items: center; gap: 12px; text-decoration: none; }
|
|
891
949
|
/* The sigil is the diamond motif scaled up with a glyph on top. The diamond is
|
|
892
950
|
a rotated pseudo-element behind the text rather than a rotated box, so the
|
|
@@ -920,8 +978,14 @@ textarea.axi-input {
|
|
|
920
978
|
.axi-tabs { display: flex; gap: 8px; }
|
|
921
979
|
/* The auto margin belongs to the masthead, not to the strip: it is a statement
|
|
922
980
|
about where the tabs sit in that row, and a strip standing on its own -
|
|
923
|
-
spanning a view instead of ending a header - has nothing to push away from.
|
|
924
|
-
|
|
981
|
+
spanning a view instead of ending a header - has nothing to push away from.
|
|
982
|
+
The overflow belongs to the masthead for the same reason. This row is capped
|
|
983
|
+
at --axi-page, so the brand, the strip, the search field and the two selects
|
|
984
|
+
divide 1244px however wide the window is, and flex-wrap breaks a line before
|
|
985
|
+
it shrinks an item - so one tab too many sent the last item in source order
|
|
986
|
+
to a second row instead of taking width off the widest one. The strip is the
|
|
987
|
+
item that can give: it scrolls, and the row stays a row. */
|
|
988
|
+
.axi-mast .axi-tabs { margin-left: auto; flex-shrink: 1; min-width: 0; overflow-x: auto; }
|
|
925
989
|
.axi-tabs a,
|
|
926
990
|
.axi-tabs button {
|
|
927
991
|
padding: 8px 16px;
|
|
@@ -1016,6 +1080,14 @@ textarea.axi-input {
|
|
|
1016
1080
|
border-right: var(--axi-border-panel) solid var(--axi-ink-line);
|
|
1017
1081
|
border-radius: 0;
|
|
1018
1082
|
}
|
|
1083
|
+
/* A rail the page scrolls behind, rather than one the page sits beside.
|
|
1084
|
+
It is the same object, so it keeps every other rule; the one thing that
|
|
1085
|
+
changes is the surface it is printed on. --axi-surface is a translucent
|
|
1086
|
+
tint under a theme like glass, which reads as a pane only while the
|
|
1087
|
+
content behind it holds still — the moment a table scrolls past, the
|
|
1088
|
+
rows show through the nav. --axi-surface-float is the surface for
|
|
1089
|
+
exactly that case, so say it once here instead of in each consumer. */
|
|
1090
|
+
.axi-rail--float { background: var(--axi-surface-float); }
|
|
1019
1091
|
.axi-rail__nav { display: flex; flex-direction: column; gap: 2px; }
|
|
1020
1092
|
.axi-rail__item {
|
|
1021
1093
|
display: flex;
|
|
@@ -1131,6 +1203,144 @@ textarea.axi-input {
|
|
|
1131
1203
|
.axi-menu__pop label:hover { background: var(--axi-ground); color: var(--axi-text); }
|
|
1132
1204
|
.axi-menu__pop input { margin: 3px 0 0; }
|
|
1133
1205
|
|
|
1206
|
+
/* ---------- command palette ---------- */
|
|
1207
|
+
/* Everything in an app, reachable by typing its name. Not .axi-modal, which is
|
|
1208
|
+
a <dialog> with a head, a body and a foot and asks you a question: a palette
|
|
1209
|
+
has no question and no buttons, it is a field with a list under it, and it
|
|
1210
|
+
sits near the top of the screen because the list has to grow downward
|
|
1211
|
+
without moving the field you are typing into.
|
|
1212
|
+
|
|
1213
|
+
Two things travel with it. The panel is .axi-surface-float, not
|
|
1214
|
+
.axi-surface: whatever the app was showing is still behind it and still
|
|
1215
|
+
scrolling, and that is the case the float token exists for. And the host
|
|
1216
|
+
element wears .axi-scrim alongside .axi-palette - the scrim already knows
|
|
1217
|
+
what darkening the page looks like, and a second spelling of it here is one
|
|
1218
|
+
more place for a theme to be restated in only one of them.
|
|
1219
|
+
|
|
1220
|
+
The cursor is not the hover. A palette is driven from the keyboard, so the
|
|
1221
|
+
row Enter would take is marked with the accent fill outright, while hover is
|
|
1222
|
+
the quieter raised-surface wash - both visible at once, saying different
|
|
1223
|
+
things, with the filled one winning on sight. The consumer sets
|
|
1224
|
+
`data-active` on the cursor row and keeps it scrolled into view. */
|
|
1225
|
+
.axi-palette {
|
|
1226
|
+
display: flex; align-items: flex-start; justify-content: center;
|
|
1227
|
+
padding: var(--axi-palette-top, 12vh) 16px 16px;
|
|
1228
|
+
}
|
|
1229
|
+
.axi-palette__panel {
|
|
1230
|
+
display: flex; flex-direction: column;
|
|
1231
|
+
width: 100%; max-width: var(--axi-palette-w, 560px);
|
|
1232
|
+
max-height: 70vh; overflow: hidden;
|
|
1233
|
+
background: var(--axi-surface-float);
|
|
1234
|
+
backdrop-filter: var(--axi-surface-filter);
|
|
1235
|
+
border: var(--axi-border-panel) solid var(--axi-ink-line);
|
|
1236
|
+
border-radius: var(--axi-radius);
|
|
1237
|
+
box-shadow: var(--axi-shadow-panel);
|
|
1238
|
+
}
|
|
1239
|
+
/* The bar holds the field and the filters. It is the raised surface so the
|
|
1240
|
+
list below it reads as the panel's contents rather than as more bar. */
|
|
1241
|
+
.axi-palette__bar {
|
|
1242
|
+
flex: none; padding: 8px 10px;
|
|
1243
|
+
background: var(--axi-surface-raised);
|
|
1244
|
+
border-bottom: var(--axi-border-control) solid var(--axi-ink-line);
|
|
1245
|
+
}
|
|
1246
|
+
/* The field is an .axi-search wrapping an .axi-input, same as anywhere else;
|
|
1247
|
+
only the height comes down, because a palette's field is a row in a bar and
|
|
1248
|
+
not a form control standing on a page. No focus ring: the palette opens with
|
|
1249
|
+
the caret already here, so a permanent outline says nothing, and it would
|
|
1250
|
+
argue with the one mark that does - the accent on the cursor row. */
|
|
1251
|
+
.axi-palette__bar .axi-input { padding-top: 7px; padding-bottom: 7px; font-size: 13px; }
|
|
1252
|
+
.axi-palette__bar .axi-input:focus-visible { outline: none; }
|
|
1253
|
+
.axi-palette__filters { display: flex; align-items: center; gap: 6px; margin-top: 8px; }
|
|
1254
|
+
/* What the list is not showing. Meta, at the end of the filter row, because it
|
|
1255
|
+
is a reading about the results and not another control among them. */
|
|
1256
|
+
.axi-palette__count {
|
|
1257
|
+
margin-left: auto; flex: none;
|
|
1258
|
+
color: var(--axi-text-faint);
|
|
1259
|
+
font: var(--axi-t-label); font-size: 10px; letter-spacing: var(--axi-ls-label);
|
|
1260
|
+
text-transform: uppercase;
|
|
1261
|
+
}
|
|
1262
|
+
/* min-height: 0 because the panel is a flex column and a list that cannot
|
|
1263
|
+
shrink below its content pushes the panel past its own max-height. The
|
|
1264
|
+
scrollbar is hidden: at twelve rows behind a fixed cap the bar is a channel
|
|
1265
|
+
carved down the side of a small panel to report a fact the row count in the
|
|
1266
|
+
bar has already given. */
|
|
1267
|
+
.axi-palette__list {
|
|
1268
|
+
flex: 1; min-height: 0; overflow-y: auto; padding: 4px 0;
|
|
1269
|
+
scrollbar-width: none;
|
|
1270
|
+
}
|
|
1271
|
+
.axi-palette__list::-webkit-scrollbar { width: 0; height: 0; }
|
|
1272
|
+
/* One .axi-palette__section per group of rows, each opening with a
|
|
1273
|
+
.axi-palette__group heading. The section is a named part rather than left to
|
|
1274
|
+
the consumer's wrapper so the rule between groups can be stated as what it
|
|
1275
|
+
is - a parting between two sections - instead of guessed at from structure. */
|
|
1276
|
+
.axi-palette__section + .axi-palette__section {
|
|
1277
|
+
border-top: var(--axi-border-hairline) solid var(--axi-rule);
|
|
1278
|
+
margin-top: 4px;
|
|
1279
|
+
}
|
|
1280
|
+
.axi-palette__group {
|
|
1281
|
+
padding: 8px 10px 4px;
|
|
1282
|
+
color: var(--axi-text-faint);
|
|
1283
|
+
font: var(--axi-t-label); font-size: 10px; letter-spacing: var(--axi-ls-label);
|
|
1284
|
+
text-transform: uppercase;
|
|
1285
|
+
}
|
|
1286
|
+
.axi-palette__row {
|
|
1287
|
+
display: flex; align-items: center; gap: 8px;
|
|
1288
|
+
width: 100%; padding: 7px 10px;
|
|
1289
|
+
background: transparent; border: 0; border-radius: 0;
|
|
1290
|
+
color: var(--axi-text-dim);
|
|
1291
|
+
font: var(--axi-t-small); text-align: left; cursor: pointer;
|
|
1292
|
+
}
|
|
1293
|
+
.axi-palette__row:hover { background: var(--axi-surface-raised); color: var(--axi-text); }
|
|
1294
|
+
.axi-palette__row[data-active] { background: var(--axi-accent); color: var(--axi-accent-ink); }
|
|
1295
|
+
/* The row's own ink is set above, but a row carries a glyph and a second line
|
|
1296
|
+
of text that were coloured on their own; on the filled row they all become
|
|
1297
|
+
the ink the fill is written in. */
|
|
1298
|
+
.axi-palette__row[data-active] svg,
|
|
1299
|
+
.axi-palette__row[data-active] .axi-palette__meta { color: var(--axi-accent-ink); }
|
|
1300
|
+
/* Where the row is, as against what it is. */
|
|
1301
|
+
.axi-palette__meta { margin-left: auto; flex: none; color: var(--axi-text-faint); font-size: 11px; }
|
|
1302
|
+
.axi-palette__empty {
|
|
1303
|
+
padding: 24px 12px; text-align: center;
|
|
1304
|
+
color: var(--axi-text-faint); font: var(--axi-t-small);
|
|
1305
|
+
}
|
|
1306
|
+
/* The palette's field at rest, wherever the app keeps the way in. It is a
|
|
1307
|
+
button, and it is drawn as the field it opens - the same ground, the same
|
|
1308
|
+
edge, the same magnifier at the head - so opening the palette reads as this
|
|
1309
|
+
control growing rather than as a different object arriving.
|
|
1310
|
+
|
|
1311
|
+
It keeps the field's fill instead of a button's because what it stands for
|
|
1312
|
+
is somewhere to type. What it takes from a button is the answer to the
|
|
1313
|
+
cursor: the block and the lift, because it is still a thing you press. */
|
|
1314
|
+
.axi-palette__trigger {
|
|
1315
|
+
display: flex; align-items: center; width: 100%; overflow: hidden;
|
|
1316
|
+
padding: 0 10px 0 0;
|
|
1317
|
+
background: var(--axi-ground);
|
|
1318
|
+
border: var(--axi-border-control) solid var(--axi-ink-line);
|
|
1319
|
+
border-radius: var(--axi-radius-sm);
|
|
1320
|
+
color: var(--axi-text-dim);
|
|
1321
|
+
font: var(--axi-t-label); font-size: 12px; font-weight: 600;
|
|
1322
|
+
cursor: pointer;
|
|
1323
|
+
transition: transform .1s, box-shadow .1s, color .1s;
|
|
1324
|
+
}
|
|
1325
|
+
.axi-palette__trigger:hover {
|
|
1326
|
+
color: var(--axi-text);
|
|
1327
|
+
box-shadow: var(--axi-shadow-control);
|
|
1328
|
+
transform: translate(-2px, -2px);
|
|
1329
|
+
}
|
|
1330
|
+
/* The magnifier at the head of the well, carrying the accent on its own. Its
|
|
1331
|
+
own element and stretched to the trigger's height so the glyph is inside the
|
|
1332
|
+
outline rather than floating in the fill. It was a solid accent block once,
|
|
1333
|
+
which at a 30px row spends a lot of the loudest colour in the palette on a
|
|
1334
|
+
control nobody is meant to look at until they want it - the glyph says the
|
|
1335
|
+
same thing at a fraction of the volume. */
|
|
1336
|
+
.axi-palette__mark {
|
|
1337
|
+
display: flex; align-items: center; align-self: stretch; flex: none;
|
|
1338
|
+
padding: 0 8px 0 10px; margin-right: 10px;
|
|
1339
|
+
color: var(--axi-accent);
|
|
1340
|
+
}
|
|
1341
|
+
.axi-palette__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1342
|
+
.axi-palette__trigger .axi-kbd { margin-left: auto; flex: none; }
|
|
1343
|
+
|
|
1134
1344
|
/* ---------- tooltip ---------- */
|
|
1135
1345
|
/* One line of ink on the darkest surface in the language, so it reads over
|
|
1136
1346
|
anything it lands on. The class draws the box only; coordinates come from
|
package/dist/themes/glass.css
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
--axi-ground-image: radial-gradient(900px 500px at 15% 0%, rgba(120, 90, 255, .30), transparent 60%), radial-gradient(700px 500px at 95% 30%, rgba(0, 190, 255, .22), transparent 62%), radial-gradient(600px 600px at 60% 100%, rgba(255, 90, 170, .20), transparent 60%);
|
|
7
7
|
--axi-surface: linear-gradient(145deg, rgba(58, 68, 92, .55), rgba(28, 33, 46, .42));
|
|
8
8
|
--axi-surface-raised: linear-gradient(145deg, rgba(86, 99, 130, .5), rgba(44, 52, 71, .38));
|
|
9
|
+
--axi-surface-float: linear-gradient(145deg, rgba(38, 45, 62, .97), rgba(22, 26, 36, .97));
|
|
9
10
|
--axi-surface-filter: blur(18px) saturate(140%);
|
|
10
11
|
--axi-ink-line: rgba(180, 200, 255, .16);
|
|
11
12
|
--axi-rule: rgba(180, 200, 255, .14);
|
package/package.json
CHANGED
package/src/primitives.css
CHANGED
|
@@ -257,6 +257,27 @@
|
|
|
257
257
|
color: var(--axi-accent); font-weight: 900; pointer-events: none;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
/* ---------- keyboard key ---------- */
|
|
261
|
+
/* A key on the keyboard, named in the interface: the Esc that closes a
|
|
262
|
+
palette, the Ctrl K that opens it. Drawn as a small control rather than as a
|
|
263
|
+
label, because what it depicts is a thing you press - so it takes the
|
|
264
|
+
control edge and the control radius, and every consumer that has a shortcut
|
|
265
|
+
to show stops having to invent a box for it.
|
|
266
|
+
|
|
267
|
+
It sits on --axi-surface, not --axi-ground: a key is raised out of whatever
|
|
268
|
+
it is printed on, and the fields and bars these appear in are already the
|
|
269
|
+
recessed thing. */
|
|
270
|
+
.axi-kbd {
|
|
271
|
+
display: inline-block;
|
|
272
|
+
padding: 2px 5px;
|
|
273
|
+
background: var(--axi-surface);
|
|
274
|
+
border: var(--axi-border-control) solid var(--axi-ink-line);
|
|
275
|
+
border-radius: var(--axi-radius-sm);
|
|
276
|
+
color: var(--axi-text-dim);
|
|
277
|
+
font: 700 10px/1 var(--axi-sans);
|
|
278
|
+
white-space: nowrap;
|
|
279
|
+
}
|
|
280
|
+
|
|
260
281
|
/* ---------- select ---------- */
|
|
261
282
|
/* The closed box is ours everywhere: strip the native control and draw the
|
|
262
283
|
caret, so a select sits alongside the other controls as just another
|
package/src/shells.css
CHANGED
|
@@ -23,6 +23,29 @@
|
|
|
23
23
|
padding-inline: var(--axi-gutter);
|
|
24
24
|
display: flex; align-items: center; gap: 22px; min-height: 66px; flex-wrap: wrap;
|
|
25
25
|
}
|
|
26
|
+
/* Above --axi-page the row stops growing: the cap is on this box, not on the
|
|
27
|
+
window, so from 1280px up the masthead has exactly one width to fit in and
|
|
28
|
+
wrapping there is not a fallback, it is a permanent second line - one tab
|
|
29
|
+
too many and the last item in source order drops, on a 4K display as surely
|
|
30
|
+
as on a laptop. So the wide row does not wrap; it takes the width out of the
|
|
31
|
+
search field instead (below), and the strip scrolls only if that runs out.
|
|
32
|
+
Under the cap the window really is too narrow and wrapping is the honest
|
|
33
|
+
answer, which is why this is a floor and not a blanket `nowrap`. */
|
|
34
|
+
@media (min-width: 1280px) {
|
|
35
|
+
.axi-mast__in { flex-wrap: nowrap; }
|
|
36
|
+
}
|
|
37
|
+
/* Nothing in the row gives up width unless it is named below. */
|
|
38
|
+
.axi-mast__in > * { flex-shrink: 0; }
|
|
39
|
+
/* The field is the one control here that is still entirely itself 70px
|
|
40
|
+
narrower - a placeholder that elides costs nothing, where a clipped tab
|
|
41
|
+
costs a destination. Flexbox shrinks proportionally, not in order, and a
|
|
42
|
+
merely-larger factor still took a couple of pixels off the last tab; the
|
|
43
|
+
number is this big to make that share round to nothing, so the effect is
|
|
44
|
+
the ordering CSS has no other way to say. Once the field is at its floor -
|
|
45
|
+
still wide enough to read as a search box rather than a smudge - whatever
|
|
46
|
+
is left over goes to the strip, which scrolls. */
|
|
47
|
+
.axi-mast .axi-search,
|
|
48
|
+
.axi-mast .docs-search { flex-shrink: 1000; min-width: 200px; }
|
|
26
49
|
.axi-brand { display: flex; align-items: center; gap: 12px; text-decoration: none; }
|
|
27
50
|
/* The sigil is the diamond motif scaled up with a glyph on top. The diamond is
|
|
28
51
|
a rotated pseudo-element behind the text rather than a rotated box, so the
|
|
@@ -56,8 +79,14 @@
|
|
|
56
79
|
.axi-tabs { display: flex; gap: 8px; }
|
|
57
80
|
/* The auto margin belongs to the masthead, not to the strip: it is a statement
|
|
58
81
|
about where the tabs sit in that row, and a strip standing on its own -
|
|
59
|
-
spanning a view instead of ending a header - has nothing to push away from.
|
|
60
|
-
|
|
82
|
+
spanning a view instead of ending a header - has nothing to push away from.
|
|
83
|
+
The overflow belongs to the masthead for the same reason. This row is capped
|
|
84
|
+
at --axi-page, so the brand, the strip, the search field and the two selects
|
|
85
|
+
divide 1244px however wide the window is, and flex-wrap breaks a line before
|
|
86
|
+
it shrinks an item - so one tab too many sent the last item in source order
|
|
87
|
+
to a second row instead of taking width off the widest one. The strip is the
|
|
88
|
+
item that can give: it scrolls, and the row stays a row. */
|
|
89
|
+
.axi-mast .axi-tabs { margin-left: auto; flex-shrink: 1; min-width: 0; overflow-x: auto; }
|
|
61
90
|
.axi-tabs a,
|
|
62
91
|
.axi-tabs button {
|
|
63
92
|
padding: 8px 16px;
|
|
@@ -152,6 +181,14 @@
|
|
|
152
181
|
border-right: var(--axi-border-panel) solid var(--axi-ink-line);
|
|
153
182
|
border-radius: 0;
|
|
154
183
|
}
|
|
184
|
+
/* A rail the page scrolls behind, rather than one the page sits beside.
|
|
185
|
+
It is the same object, so it keeps every other rule; the one thing that
|
|
186
|
+
changes is the surface it is printed on. --axi-surface is a translucent
|
|
187
|
+
tint under a theme like glass, which reads as a pane only while the
|
|
188
|
+
content behind it holds still — the moment a table scrolls past, the
|
|
189
|
+
rows show through the nav. --axi-surface-float is the surface for
|
|
190
|
+
exactly that case, so say it once here instead of in each consumer. */
|
|
191
|
+
.axi-rail--float { background: var(--axi-surface-float); }
|
|
155
192
|
.axi-rail__nav { display: flex; flex-direction: column; gap: 2px; }
|
|
156
193
|
.axi-rail__item {
|
|
157
194
|
display: flex;
|
|
@@ -267,6 +304,144 @@
|
|
|
267
304
|
.axi-menu__pop label:hover { background: var(--axi-ground); color: var(--axi-text); }
|
|
268
305
|
.axi-menu__pop input { margin: 3px 0 0; }
|
|
269
306
|
|
|
307
|
+
/* ---------- command palette ---------- */
|
|
308
|
+
/* Everything in an app, reachable by typing its name. Not .axi-modal, which is
|
|
309
|
+
a <dialog> with a head, a body and a foot and asks you a question: a palette
|
|
310
|
+
has no question and no buttons, it is a field with a list under it, and it
|
|
311
|
+
sits near the top of the screen because the list has to grow downward
|
|
312
|
+
without moving the field you are typing into.
|
|
313
|
+
|
|
314
|
+
Two things travel with it. The panel is .axi-surface-float, not
|
|
315
|
+
.axi-surface: whatever the app was showing is still behind it and still
|
|
316
|
+
scrolling, and that is the case the float token exists for. And the host
|
|
317
|
+
element wears .axi-scrim alongside .axi-palette - the scrim already knows
|
|
318
|
+
what darkening the page looks like, and a second spelling of it here is one
|
|
319
|
+
more place for a theme to be restated in only one of them.
|
|
320
|
+
|
|
321
|
+
The cursor is not the hover. A palette is driven from the keyboard, so the
|
|
322
|
+
row Enter would take is marked with the accent fill outright, while hover is
|
|
323
|
+
the quieter raised-surface wash - both visible at once, saying different
|
|
324
|
+
things, with the filled one winning on sight. The consumer sets
|
|
325
|
+
`data-active` on the cursor row and keeps it scrolled into view. */
|
|
326
|
+
.axi-palette {
|
|
327
|
+
display: flex; align-items: flex-start; justify-content: center;
|
|
328
|
+
padding: var(--axi-palette-top, 12vh) 16px 16px;
|
|
329
|
+
}
|
|
330
|
+
.axi-palette__panel {
|
|
331
|
+
display: flex; flex-direction: column;
|
|
332
|
+
width: 100%; max-width: var(--axi-palette-w, 560px);
|
|
333
|
+
max-height: 70vh; overflow: hidden;
|
|
334
|
+
background: var(--axi-surface-float);
|
|
335
|
+
backdrop-filter: var(--axi-surface-filter);
|
|
336
|
+
border: var(--axi-border-panel) solid var(--axi-ink-line);
|
|
337
|
+
border-radius: var(--axi-radius);
|
|
338
|
+
box-shadow: var(--axi-shadow-panel);
|
|
339
|
+
}
|
|
340
|
+
/* The bar holds the field and the filters. It is the raised surface so the
|
|
341
|
+
list below it reads as the panel's contents rather than as more bar. */
|
|
342
|
+
.axi-palette__bar {
|
|
343
|
+
flex: none; padding: 8px 10px;
|
|
344
|
+
background: var(--axi-surface-raised);
|
|
345
|
+
border-bottom: var(--axi-border-control) solid var(--axi-ink-line);
|
|
346
|
+
}
|
|
347
|
+
/* The field is an .axi-search wrapping an .axi-input, same as anywhere else;
|
|
348
|
+
only the height comes down, because a palette's field is a row in a bar and
|
|
349
|
+
not a form control standing on a page. No focus ring: the palette opens with
|
|
350
|
+
the caret already here, so a permanent outline says nothing, and it would
|
|
351
|
+
argue with the one mark that does - the accent on the cursor row. */
|
|
352
|
+
.axi-palette__bar .axi-input { padding-top: 7px; padding-bottom: 7px; font-size: 13px; }
|
|
353
|
+
.axi-palette__bar .axi-input:focus-visible { outline: none; }
|
|
354
|
+
.axi-palette__filters { display: flex; align-items: center; gap: 6px; margin-top: 8px; }
|
|
355
|
+
/* What the list is not showing. Meta, at the end of the filter row, because it
|
|
356
|
+
is a reading about the results and not another control among them. */
|
|
357
|
+
.axi-palette__count {
|
|
358
|
+
margin-left: auto; flex: none;
|
|
359
|
+
color: var(--axi-text-faint);
|
|
360
|
+
font: var(--axi-t-label); font-size: 10px; letter-spacing: var(--axi-ls-label);
|
|
361
|
+
text-transform: uppercase;
|
|
362
|
+
}
|
|
363
|
+
/* min-height: 0 because the panel is a flex column and a list that cannot
|
|
364
|
+
shrink below its content pushes the panel past its own max-height. The
|
|
365
|
+
scrollbar is hidden: at twelve rows behind a fixed cap the bar is a channel
|
|
366
|
+
carved down the side of a small panel to report a fact the row count in the
|
|
367
|
+
bar has already given. */
|
|
368
|
+
.axi-palette__list {
|
|
369
|
+
flex: 1; min-height: 0; overflow-y: auto; padding: 4px 0;
|
|
370
|
+
scrollbar-width: none;
|
|
371
|
+
}
|
|
372
|
+
.axi-palette__list::-webkit-scrollbar { width: 0; height: 0; }
|
|
373
|
+
/* One .axi-palette__section per group of rows, each opening with a
|
|
374
|
+
.axi-palette__group heading. The section is a named part rather than left to
|
|
375
|
+
the consumer's wrapper so the rule between groups can be stated as what it
|
|
376
|
+
is - a parting between two sections - instead of guessed at from structure. */
|
|
377
|
+
.axi-palette__section + .axi-palette__section {
|
|
378
|
+
border-top: var(--axi-border-hairline) solid var(--axi-rule);
|
|
379
|
+
margin-top: 4px;
|
|
380
|
+
}
|
|
381
|
+
.axi-palette__group {
|
|
382
|
+
padding: 8px 10px 4px;
|
|
383
|
+
color: var(--axi-text-faint);
|
|
384
|
+
font: var(--axi-t-label); font-size: 10px; letter-spacing: var(--axi-ls-label);
|
|
385
|
+
text-transform: uppercase;
|
|
386
|
+
}
|
|
387
|
+
.axi-palette__row {
|
|
388
|
+
display: flex; align-items: center; gap: 8px;
|
|
389
|
+
width: 100%; padding: 7px 10px;
|
|
390
|
+
background: transparent; border: 0; border-radius: 0;
|
|
391
|
+
color: var(--axi-text-dim);
|
|
392
|
+
font: var(--axi-t-small); text-align: left; cursor: pointer;
|
|
393
|
+
}
|
|
394
|
+
.axi-palette__row:hover { background: var(--axi-surface-raised); color: var(--axi-text); }
|
|
395
|
+
.axi-palette__row[data-active] { background: var(--axi-accent); color: var(--axi-accent-ink); }
|
|
396
|
+
/* The row's own ink is set above, but a row carries a glyph and a second line
|
|
397
|
+
of text that were coloured on their own; on the filled row they all become
|
|
398
|
+
the ink the fill is written in. */
|
|
399
|
+
.axi-palette__row[data-active] svg,
|
|
400
|
+
.axi-palette__row[data-active] .axi-palette__meta { color: var(--axi-accent-ink); }
|
|
401
|
+
/* Where the row is, as against what it is. */
|
|
402
|
+
.axi-palette__meta { margin-left: auto; flex: none; color: var(--axi-text-faint); font-size: 11px; }
|
|
403
|
+
.axi-palette__empty {
|
|
404
|
+
padding: 24px 12px; text-align: center;
|
|
405
|
+
color: var(--axi-text-faint); font: var(--axi-t-small);
|
|
406
|
+
}
|
|
407
|
+
/* The palette's field at rest, wherever the app keeps the way in. It is a
|
|
408
|
+
button, and it is drawn as the field it opens - the same ground, the same
|
|
409
|
+
edge, the same magnifier at the head - so opening the palette reads as this
|
|
410
|
+
control growing rather than as a different object arriving.
|
|
411
|
+
|
|
412
|
+
It keeps the field's fill instead of a button's because what it stands for
|
|
413
|
+
is somewhere to type. What it takes from a button is the answer to the
|
|
414
|
+
cursor: the block and the lift, because it is still a thing you press. */
|
|
415
|
+
.axi-palette__trigger {
|
|
416
|
+
display: flex; align-items: center; width: 100%; overflow: hidden;
|
|
417
|
+
padding: 0 10px 0 0;
|
|
418
|
+
background: var(--axi-ground);
|
|
419
|
+
border: var(--axi-border-control) solid var(--axi-ink-line);
|
|
420
|
+
border-radius: var(--axi-radius-sm);
|
|
421
|
+
color: var(--axi-text-dim);
|
|
422
|
+
font: var(--axi-t-label); font-size: 12px; font-weight: 600;
|
|
423
|
+
cursor: pointer;
|
|
424
|
+
transition: transform .1s, box-shadow .1s, color .1s;
|
|
425
|
+
}
|
|
426
|
+
.axi-palette__trigger:hover {
|
|
427
|
+
color: var(--axi-text);
|
|
428
|
+
box-shadow: var(--axi-shadow-control);
|
|
429
|
+
transform: translate(-2px, -2px);
|
|
430
|
+
}
|
|
431
|
+
/* The magnifier at the head of the well, carrying the accent on its own. Its
|
|
432
|
+
own element and stretched to the trigger's height so the glyph is inside the
|
|
433
|
+
outline rather than floating in the fill. It was a solid accent block once,
|
|
434
|
+
which at a 30px row spends a lot of the loudest colour in the palette on a
|
|
435
|
+
control nobody is meant to look at until they want it - the glyph says the
|
|
436
|
+
same thing at a fraction of the volume. */
|
|
437
|
+
.axi-palette__mark {
|
|
438
|
+
display: flex; align-items: center; align-self: stretch; flex: none;
|
|
439
|
+
padding: 0 8px 0 10px; margin-right: 10px;
|
|
440
|
+
color: var(--axi-accent);
|
|
441
|
+
}
|
|
442
|
+
.axi-palette__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
443
|
+
.axi-palette__trigger .axi-kbd { margin-left: auto; flex: none; }
|
|
444
|
+
|
|
270
445
|
/* ---------- tooltip ---------- */
|
|
271
446
|
/* One line of ink on the darkest surface in the language, so it reads over
|
|
272
447
|
anything it lands on. The class draws the box only; coordinates come from
|
package/src/tokens.css
CHANGED
|
@@ -28,6 +28,20 @@
|
|
|
28
28
|
--axi-ground-image: none;
|
|
29
29
|
--axi-surface: #222731; /* a panel raised off the page */
|
|
30
30
|
--axi-surface-raised: #2b313d; /* a chip or popover raised off a panel */
|
|
31
|
+
/* The fill for a surface that FLOATS over content which scrolls behind it: a
|
|
32
|
+
command palette, a menu popover, a modal. Holds --axi-surface, because for
|
|
33
|
+
a theme whose panels are opaque a floating panel is just a panel and there
|
|
34
|
+
is nothing to decide.
|
|
35
|
+
|
|
36
|
+
It exists for the theme whose panels are not. A translucent pane is legible
|
|
37
|
+
standing still on the page and stops being legible the moment a table
|
|
38
|
+
scrolls under it - the text it is carrying now has text behind it. The blur
|
|
39
|
+
that was supposed to rescue that is not something a language can promise:
|
|
40
|
+
backdrop-filter is unavailable or switched off on whole platforms, and
|
|
41
|
+
where it is missing the alpha is the only thing left. So the float gets its
|
|
42
|
+
own token, and a theme that goes translucent restates it near-opaque
|
|
43
|
+
without giving up the tint on everything that merely sits still. */
|
|
44
|
+
--axi-surface-float: var(--axi-surface);
|
|
31
45
|
--axi-ink-line: #0c0e12; /* the outline every raised element is drawn with */
|
|
32
46
|
/* The tone a shape is made of when it is made of the line ink rather than
|
|
33
47
|
outlined in it - the tooltip, the titlebar strip, the switch's slug. Holds
|