@axiapps/axi-design 1.19.0 → 1.21.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/dist/axi.css CHANGED
@@ -144,6 +144,14 @@
144
144
  control-sized reads --axi-radius-sm. */
145
145
  --axi-radius: 0;
146
146
  --axi-radius-sm: 0;
147
+ /* The third step, for a box the size of the text beside it: a checkbox, a
148
+ radio. --axi-radius-sm is sized for something you press - a button, an
149
+ input, 36px tall - and a theme is free to set it to 10px. On a 22px
150
+ square 10px is most of the box: the checkbox stops being a square and
151
+ becomes a circle, and a circle next to rule 7's diamond radio is the one
152
+ shape confusion this pair exists to avoid. A theme that wants soft
153
+ corners sets all three. */
154
+ --axi-radius-xs: 0;
147
155
  /* Not a form step: a hairline used inside prose (inline code, table rules,
148
156
  the list bullet) where the two form steps above would read as too heavy
149
157
  for a line of running text. */
@@ -286,6 +294,15 @@ a { color: inherit; }
286
294
  fit is genuinely called for. */
287
295
  padding: var(--axi-panel-pad, 26px);
288
296
  }
297
+ /* A panel that floats over moving content rather than sitting in the page: a
298
+ readout pinned to a chart, a popover over a scrolling table. Same object,
299
+ same everything else; the one thing that changes is the surface it is
300
+ printed on. --axi-surface is a translucent tint under a theme like glass,
301
+ which reads as a pane only while what is behind it holds still - the moment
302
+ the content scrolls, it shows through the panel. --axi-surface-float is the
303
+ surface for exactly that case. Says the same thing .axi-rail--float says,
304
+ on the other surface that needs it. */
305
+ .axi-panel--float { background: var(--axi-surface-float); }
289
306
 
290
307
  /* ---------- well ---------- */
291
308
  /* The panel's inverse: a field sunk into the surface around it rather than
@@ -795,7 +812,10 @@ a { color: inherit; }
795
812
  place-items: center;
796
813
  background: var(--axi-ground);
797
814
  border: var(--axi-border-control) solid var(--axi-ink-line);
798
- border-radius: var(--axi-radius-sm);
815
+ /* The text-sized step, not the control-sized one - see --axi-radius-xs in
816
+ src/tokens.css for why a 22px box cannot take a corner scaled for a 36px
817
+ button. */
818
+ border-radius: var(--axi-radius-xs);
799
819
  cursor: pointer;
800
820
  }
801
821
  /* The mark is always in the DOM and revealed with opacity rather than being
@@ -1366,6 +1386,22 @@ textarea.axi-input {
1366
1386
  white-space: nowrap;
1367
1387
  pointer-events: none;
1368
1388
  }
1389
+ /* Positioned by the thing it describes rather than by the cursor. The <body>
1390
+ contract above is what position: fixed costs; a tooltip that belongs to one
1391
+ trigger can be a child of it instead, and then it wants the trigger's
1392
+ coordinate space. Rule 4's transformed ancestor is the reason fixed cannot
1393
+ live there - absolute is anchored to exactly that ancestor, so a hover lift
1394
+ carries the tooltip with it instead of stranding it.
1395
+
1396
+ The consumer supplies the offsets (a `position: relative` trigger and the
1397
+ left/top or inset the placement needs); this only changes which box those
1398
+ offsets are measured from. */
1399
+ .axi-tooltip--anchored { position: absolute; }
1400
+ /* A tooltip carrying a sentence rather than a value. nowrap is right for a
1401
+ reading - a number that breaks across two lines is harder to read than one
1402
+ that runs past its trigger - and wrong for prose, which needs a width from
1403
+ the consumer to wrap inside. */
1404
+ .axi-tooltip--wrap { white-space: normal; }
1369
1405
 
1370
1406
  /* ---------- card ---------- */
1371
1407
  .axi-card {
@@ -1806,7 +1842,11 @@ textarea.axi-input {
1806
1842
  .axi-table td { text-align: right; white-space: nowrap; }
1807
1843
  .axi-table th:first-child,
1808
1844
  .axi-table td:first-child { text-align: left; }
1809
- .axi-table th {
1845
+ /* `thead th`, not `th`. A row's name is a `<th scope="row">` - that is what
1846
+ tells a screen reader which cell labels the row - and it is a name, not a
1847
+ column heading: uppercase micro type down the first column would shout every
1848
+ row's label at the reader. A row header takes the cell treatment below. */
1849
+ .axi-table thead th {
1810
1850
  padding: 0 10px 10px;
1811
1851
  font: var(--axi-t-micro);
1812
1852
  letter-spacing: var(--axi-ls-micro);
@@ -1814,7 +1854,8 @@ textarea.axi-input {
1814
1854
  color: var(--axi-text-faint);
1815
1855
  border-bottom: var(--axi-border-control) solid var(--axi-rule);
1816
1856
  }
1817
- .axi-table td {
1857
+ .axi-table td,
1858
+ .axi-table tbody th {
1818
1859
  padding: 9px 10px;
1819
1860
  font: var(--axi-t-small);
1820
1861
  font-weight: 700;
@@ -1825,7 +1866,7 @@ textarea.axi-input {
1825
1866
  hovering a row is not a status, and forty rows that each flash a colour on
1826
1867
  the way past the one you want is the tinted-everything failure rule 2 is
1827
1868
  about. */
1828
- .axi-table tbody tr:hover td { background: var(--axi-surface-raised); color: var(--axi-text); }
1869
+ .axi-table tbody tr:hover :is(td, th) { background: var(--axi-surface-raised); color: var(--axi-text); }
1829
1870
  /* The measured value in a row, as opposed to its supporting numbers. */
1830
1871
  .axi-table__num { color: var(--axi-text); }
1831
1872
  /* A name cell: an icon, a diamond or a rank beside the label. */
@@ -1843,6 +1884,93 @@ textarea.axi-input {
1843
1884
  }
1844
1885
  .axi-table__rank--top { background: var(--axi-accent); color: var(--axi-accent-ink); }
1845
1886
 
1887
+ /* A table too wide and too tall for the panel holding it: twenty numeric
1888
+ columns against forty players, scrolled in both directions. Rule 8 still
1889
+ decides what every line means - the head is a lid at the control step, the
1890
+ rows are hairlines - and the only new thing here is that the lid and the
1891
+ row's name have to stay put while the numbers move under them.
1892
+
1893
+ `border-collapse: separate` is load-bearing, not housekeeping. A collapsed
1894
+ table hoists its cell borders onto the table itself, and a border owned by
1895
+ the table scrolls away with it, so a sticky head drawn under `collapse`
1896
+ loses the very line that makes it a lid. Separate borders stay with the
1897
+ cell that declared them. Each modifier sets it, so either works alone.
1898
+
1899
+ Note what is NOT here. No block: an edge that has to hold under scrolling
1900
+ is a border, and a block in this language is an offset rectangle, which is
1901
+ not the shape of the top of a column. No scrolled-under state either - the
1902
+ head's edge is drawn whether anything has moved beneath it or not, so there
1903
+ is nothing for scrolling to reveal. */
1904
+ /* The isolation is the reason the three z-indexes below can be 1, 2 and 3:
1905
+ they are resolved in here and cannot compete with the page stack. See the
1906
+ layer table in docs/RULES.md. */
1907
+ .axi-table__scroll { overflow: auto; isolation: isolate; }
1908
+ /* The head stays. */
1909
+ .axi-table--sticky { border-collapse: separate; border-spacing: 0; }
1910
+ .axi-table--sticky thead th {
1911
+ position: sticky;
1912
+ top: 0;
1913
+ z-index: 2;
1914
+ /* The float surface, not the raised one, and this is the whole reason
1915
+ --axi-surface-float exists: rows travel behind both of these, and a glass
1916
+ theme's ordinary surfaces are alpha, so a head at --axi-surface-raised
1917
+ would have numbers sliding through it. Opaque is not a look here, it is
1918
+ the requirement. */
1919
+ background: var(--axi-surface-float);
1920
+ border-bottom: var(--axi-border-control) solid var(--axi-ink-line);
1921
+ }
1922
+ /* The first column stays. It holds the row's name, and a name scrolled out of
1923
+ sight makes every number left of it anonymous - which is the whole reason a
1924
+ wide table is readable at all. Its edge is the cut the head carries, turned
1925
+ ninety degrees. */
1926
+ .axi-table--pinned { border-collapse: separate; border-spacing: 0; }
1927
+ .axi-table--pinned :is(thead, tbody) :is(th, td):first-child {
1928
+ position: sticky;
1929
+ left: 0;
1930
+ z-index: 1;
1931
+ background: var(--axi-surface-float);
1932
+ border-right: var(--axi-border-control) solid var(--axi-ink-line);
1933
+ }
1934
+ /* The corner belongs to both and has to outrank both. */
1935
+ .axi-table--sticky.axi-table--pinned thead th:first-child { z-index: 3; }
1936
+ /* Twenty columns do not get a leaderboard's padding. The type steps down with
1937
+ them; everything else about the table is unchanged, which is the point of
1938
+ its being a modifier rather than a second table. */
1939
+ .axi-table--dense thead th { padding: 0 7px 6px; }
1940
+ .axi-table--dense :is(td, tbody th) { padding: 5px 7px; font: var(--axi-t-micro); }
1941
+ /* The sorted column, named with the attribute a screen reader already needs
1942
+ in order to announce it. A class beside `aria-sort` would be a second
1943
+ source of truth for one fact. The mark is ink on the head and its edge -
1944
+ not a fill down the column, which is rule 2's tinted-everything failure
1945
+ applied to the one column you are trying to read. */
1946
+ .axi-table th[aria-sort] {
1947
+ color: var(--axi-accent);
1948
+ border-bottom-color: var(--axi-accent);
1949
+ }
1950
+ /* CSS cannot reach "the cells under the sorted head", so the consumer marks
1951
+ them. Full-strength text against the column's dimmed neighbours. */
1952
+ .axi-table__cell--sorted { color: var(--axi-text); }
1953
+ /* A sortable heading is a button, because sorting is an action and a <th> you
1954
+ can click but not tab to is a column the keyboard cannot sort. It inherits
1955
+ everything and adds nothing, so a heading looks the same whether it sorts or
1956
+ not - the only thing that marks a sorted column is aria-sort above. */
1957
+ .axi-table__sort {
1958
+ display: flex;
1959
+ align-items: center;
1960
+ justify-content: flex-end;
1961
+ gap: 6px;
1962
+ width: 100%;
1963
+ padding: 0;
1964
+ background: none;
1965
+ border: 0;
1966
+ font: inherit;
1967
+ color: inherit;
1968
+ letter-spacing: inherit;
1969
+ text-transform: inherit;
1970
+ cursor: pointer;
1971
+ }
1972
+ .axi-table th:first-child .axi-table__sort { justify-content: flex-start; }
1973
+
1846
1974
  /* ---------- meter ---------- */
1847
1975
  /* Rule 9: a proportion is a length. The track is the ground, the fill is the
1848
1976
  value, and the fill is one ink at full strength - a tinted or faded bar is
@@ -27,4 +27,5 @@
27
27
  --axi-border-hairline: 1px;
28
28
  --axi-radius: 16px;
29
29
  --axi-radius-sm: 10px;
30
+ --axi-radius-xs: 4px;
30
31
  }
package/docs/RULES.md CHANGED
@@ -461,6 +461,9 @@ instead of joining it.
461
461
 
462
462
  | Layer | z-index | What sits here |
463
463
  |---|---|---|
464
+ | Pinned column | 1 | `.axi-table--pinned`'s first column |
465
+ | Sticky head | 2 | `.axi-table--sticky`'s `thead th` |
466
+ | Table corner | 3 | where the two cross |
464
467
  | Sticky chrome | 40 | `.axi-mast` |
465
468
  | Popovers | 41 | `.axi-menu__pop`, `.axi-picker__pop` |
466
469
  | Scrim | 50 | `.axi-scrim` |
@@ -473,6 +476,15 @@ The modal has no number on purpose. A `<dialog>` opened with `showModal()` is
473
476
  promoted to the browser's top layer, which sits above every `z-index` there is;
474
477
  writing a number in that row would describe a competition the modal is not in.
475
478
 
479
+ The first three are sealed rather than low. `.axi-table__scroll` sets
480
+ `isolation: isolate`, so those numbers are resolved inside the scroll container
481
+ and never compete with the page layers under them - a table's sticky head
482
+ cannot climb over a mast, and a table inside a drawer cannot reach out of it.
483
+ They are listed because a table needs three orderings among its own cells and a
484
+ number that is never written down is a number that drifts; the isolation is
485
+ what keeps 1, 2 and 3 from meaning anything outside the table that declares
486
+ them.
487
+
476
488
  A negative `z-index` inside a component's own `isolation` context — the sigil's
477
489
  backing shape — is not a layer and is not listed. It is invisible outside the
478
490
  component that owns it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiapps/axi-design",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "description": "The design language for the axi suite — flat and outlined, dark, drawn in saturated ink.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/data.css CHANGED
@@ -64,7 +64,11 @@
64
64
  .axi-table td { text-align: right; white-space: nowrap; }
65
65
  .axi-table th:first-child,
66
66
  .axi-table td:first-child { text-align: left; }
67
- .axi-table th {
67
+ /* `thead th`, not `th`. A row's name is a `<th scope="row">` - that is what
68
+ tells a screen reader which cell labels the row - and it is a name, not a
69
+ column heading: uppercase micro type down the first column would shout every
70
+ row's label at the reader. A row header takes the cell treatment below. */
71
+ .axi-table thead th {
68
72
  padding: 0 10px 10px;
69
73
  font: var(--axi-t-micro);
70
74
  letter-spacing: var(--axi-ls-micro);
@@ -72,7 +76,8 @@
72
76
  color: var(--axi-text-faint);
73
77
  border-bottom: var(--axi-border-control) solid var(--axi-rule);
74
78
  }
75
- .axi-table td {
79
+ .axi-table td,
80
+ .axi-table tbody th {
76
81
  padding: 9px 10px;
77
82
  font: var(--axi-t-small);
78
83
  font-weight: 700;
@@ -83,7 +88,7 @@
83
88
  hovering a row is not a status, and forty rows that each flash a colour on
84
89
  the way past the one you want is the tinted-everything failure rule 2 is
85
90
  about. */
86
- .axi-table tbody tr:hover td { background: var(--axi-surface-raised); color: var(--axi-text); }
91
+ .axi-table tbody tr:hover :is(td, th) { background: var(--axi-surface-raised); color: var(--axi-text); }
87
92
  /* The measured value in a row, as opposed to its supporting numbers. */
88
93
  .axi-table__num { color: var(--axi-text); }
89
94
  /* A name cell: an icon, a diamond or a rank beside the label. */
@@ -101,6 +106,93 @@
101
106
  }
102
107
  .axi-table__rank--top { background: var(--axi-accent); color: var(--axi-accent-ink); }
103
108
 
109
+ /* A table too wide and too tall for the panel holding it: twenty numeric
110
+ columns against forty players, scrolled in both directions. Rule 8 still
111
+ decides what every line means - the head is a lid at the control step, the
112
+ rows are hairlines - and the only new thing here is that the lid and the
113
+ row's name have to stay put while the numbers move under them.
114
+
115
+ `border-collapse: separate` is load-bearing, not housekeeping. A collapsed
116
+ table hoists its cell borders onto the table itself, and a border owned by
117
+ the table scrolls away with it, so a sticky head drawn under `collapse`
118
+ loses the very line that makes it a lid. Separate borders stay with the
119
+ cell that declared them. Each modifier sets it, so either works alone.
120
+
121
+ Note what is NOT here. No block: an edge that has to hold under scrolling
122
+ is a border, and a block in this language is an offset rectangle, which is
123
+ not the shape of the top of a column. No scrolled-under state either - the
124
+ head's edge is drawn whether anything has moved beneath it or not, so there
125
+ is nothing for scrolling to reveal. */
126
+ /* The isolation is the reason the three z-indexes below can be 1, 2 and 3:
127
+ they are resolved in here and cannot compete with the page stack. See the
128
+ layer table in docs/RULES.md. */
129
+ .axi-table__scroll { overflow: auto; isolation: isolate; }
130
+ /* The head stays. */
131
+ .axi-table--sticky { border-collapse: separate; border-spacing: 0; }
132
+ .axi-table--sticky thead th {
133
+ position: sticky;
134
+ top: 0;
135
+ z-index: 2;
136
+ /* The float surface, not the raised one, and this is the whole reason
137
+ --axi-surface-float exists: rows travel behind both of these, and a glass
138
+ theme's ordinary surfaces are alpha, so a head at --axi-surface-raised
139
+ would have numbers sliding through it. Opaque is not a look here, it is
140
+ the requirement. */
141
+ background: var(--axi-surface-float);
142
+ border-bottom: var(--axi-border-control) solid var(--axi-ink-line);
143
+ }
144
+ /* The first column stays. It holds the row's name, and a name scrolled out of
145
+ sight makes every number left of it anonymous - which is the whole reason a
146
+ wide table is readable at all. Its edge is the cut the head carries, turned
147
+ ninety degrees. */
148
+ .axi-table--pinned { border-collapse: separate; border-spacing: 0; }
149
+ .axi-table--pinned :is(thead, tbody) :is(th, td):first-child {
150
+ position: sticky;
151
+ left: 0;
152
+ z-index: 1;
153
+ background: var(--axi-surface-float);
154
+ border-right: var(--axi-border-control) solid var(--axi-ink-line);
155
+ }
156
+ /* The corner belongs to both and has to outrank both. */
157
+ .axi-table--sticky.axi-table--pinned thead th:first-child { z-index: 3; }
158
+ /* Twenty columns do not get a leaderboard's padding. The type steps down with
159
+ them; everything else about the table is unchanged, which is the point of
160
+ its being a modifier rather than a second table. */
161
+ .axi-table--dense thead th { padding: 0 7px 6px; }
162
+ .axi-table--dense :is(td, tbody th) { padding: 5px 7px; font: var(--axi-t-micro); }
163
+ /* The sorted column, named with the attribute a screen reader already needs
164
+ in order to announce it. A class beside `aria-sort` would be a second
165
+ source of truth for one fact. The mark is ink on the head and its edge -
166
+ not a fill down the column, which is rule 2's tinted-everything failure
167
+ applied to the one column you are trying to read. */
168
+ .axi-table th[aria-sort] {
169
+ color: var(--axi-accent);
170
+ border-bottom-color: var(--axi-accent);
171
+ }
172
+ /* CSS cannot reach "the cells under the sorted head", so the consumer marks
173
+ them. Full-strength text against the column's dimmed neighbours. */
174
+ .axi-table__cell--sorted { color: var(--axi-text); }
175
+ /* A sortable heading is a button, because sorting is an action and a <th> you
176
+ can click but not tab to is a column the keyboard cannot sort. It inherits
177
+ everything and adds nothing, so a heading looks the same whether it sorts or
178
+ not - the only thing that marks a sorted column is aria-sort above. */
179
+ .axi-table__sort {
180
+ display: flex;
181
+ align-items: center;
182
+ justify-content: flex-end;
183
+ gap: 6px;
184
+ width: 100%;
185
+ padding: 0;
186
+ background: none;
187
+ border: 0;
188
+ font: inherit;
189
+ color: inherit;
190
+ letter-spacing: inherit;
191
+ text-transform: inherit;
192
+ cursor: pointer;
193
+ }
194
+ .axi-table th:first-child .axi-table__sort { justify-content: flex-start; }
195
+
104
196
  /* ---------- meter ---------- */
105
197
  /* Rule 9: a proportion is a length. The track is the ground, the fill is the
106
198
  value, and the fill is one ink at full strength - a tinted or faded bar is
package/src/forms.css CHANGED
@@ -36,7 +36,10 @@
36
36
  place-items: center;
37
37
  background: var(--axi-ground);
38
38
  border: var(--axi-border-control) solid var(--axi-ink-line);
39
- border-radius: var(--axi-radius-sm);
39
+ /* The text-sized step, not the control-sized one - see --axi-radius-xs in
40
+ src/tokens.css for why a 22px box cannot take a corner scaled for a 36px
41
+ button. */
42
+ border-radius: var(--axi-radius-xs);
40
43
  cursor: pointer;
41
44
  }
42
45
  /* The mark is always in the DOM and revealed with opacity rather than being
@@ -28,6 +28,15 @@
28
28
  fit is genuinely called for. */
29
29
  padding: var(--axi-panel-pad, 26px);
30
30
  }
31
+ /* A panel that floats over moving content rather than sitting in the page: a
32
+ readout pinned to a chart, a popover over a scrolling table. Same object,
33
+ same everything else; the one thing that changes is the surface it is
34
+ printed on. --axi-surface is a translucent tint under a theme like glass,
35
+ which reads as a pane only while what is behind it holds still - the moment
36
+ the content scrolls, it shows through the panel. --axi-surface-float is the
37
+ surface for exactly that case. Says the same thing .axi-rail--float says,
38
+ on the other surface that needs it. */
39
+ .axi-panel--float { background: var(--axi-surface-float); }
31
40
 
32
41
  /* ---------- well ---------- */
33
42
  /* The panel's inverse: a field sunk into the surface around it rather than
package/src/shells.css CHANGED
@@ -467,6 +467,22 @@
467
467
  white-space: nowrap;
468
468
  pointer-events: none;
469
469
  }
470
+ /* Positioned by the thing it describes rather than by the cursor. The <body>
471
+ contract above is what position: fixed costs; a tooltip that belongs to one
472
+ trigger can be a child of it instead, and then it wants the trigger's
473
+ coordinate space. Rule 4's transformed ancestor is the reason fixed cannot
474
+ live there - absolute is anchored to exactly that ancestor, so a hover lift
475
+ carries the tooltip with it instead of stranding it.
476
+
477
+ The consumer supplies the offsets (a `position: relative` trigger and the
478
+ left/top or inset the placement needs); this only changes which box those
479
+ offsets are measured from. */
480
+ .axi-tooltip--anchored { position: absolute; }
481
+ /* A tooltip carrying a sentence rather than a value. nowrap is right for a
482
+ reading - a number that breaks across two lines is harder to read than one
483
+ that runs past its trigger - and wrong for prose, which needs a width from
484
+ the consumer to wrap inside. */
485
+ .axi-tooltip--wrap { white-space: normal; }
470
486
 
471
487
  /* ---------- card ---------- */
472
488
  .axi-card {
package/src/tokens.css CHANGED
@@ -140,6 +140,14 @@
140
140
  control-sized reads --axi-radius-sm. */
141
141
  --axi-radius: 0;
142
142
  --axi-radius-sm: 0;
143
+ /* The third step, for a box the size of the text beside it: a checkbox, a
144
+ radio. --axi-radius-sm is sized for something you press - a button, an
145
+ input, 36px tall - and a theme is free to set it to 10px. On a 22px
146
+ square 10px is most of the box: the checkbox stops being a square and
147
+ becomes a circle, and a circle next to rule 7's diamond radio is the one
148
+ shape confusion this pair exists to avoid. A theme that wants soft
149
+ corners sets all three. */
150
+ --axi-radius-xs: 0;
143
151
  /* Not a form step: a hairline used inside prose (inline code, table rules,
144
152
  the list bullet) where the two form steps above would read as too heavy
145
153
  for a line of running text. */