@axiapps/axi-design 1.46.0 → 1.48.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 +159 -0
- package/dist/themes/flat.css +4 -0
- package/dist/themes/glass.css +5 -1
- package/docs/RULES.md +13 -4
- package/package.json +1 -1
- package/src/primitives.css +146 -0
- package/src/shells.css +13 -0
package/dist/axi.css
CHANGED
|
@@ -727,6 +727,152 @@ button.axi-panel--tile:hover,
|
|
|
727
727
|
.axi-btn--xs { --axi-btn-pad: 5px 9px; --axi-btn-size: 11px; }
|
|
728
728
|
.axi-btn--icon { --axi-btn-pad: 7px; }
|
|
729
729
|
|
|
730
|
+
/* ---------- split button ---------- */
|
|
731
|
+
/* One control with two targets: a primary action and a disclosure half that
|
|
732
|
+
opens a menu of its variants. The name is `.axi-btn-split` and not
|
|
733
|
+
`.axi-split` because that one is already the split *pane* in
|
|
734
|
+
src/layout.css - a pane and a button are not the same noun, and the
|
|
735
|
+
language only gets one of them per name.
|
|
736
|
+
|
|
737
|
+
The whole component exists because a consumer hand-wrote it and got the
|
|
738
|
+
radius wrong, which was not carelessness: a split button is the one place
|
|
739
|
+
where a corner is sometimes square and sometimes the surface's, so it is
|
|
740
|
+
the one place where a literal gets written. That consumer's control was
|
|
741
|
+
rounded at 10px on all three surfaces, correct on glass and wrong on the
|
|
742
|
+
other two, while every ordinary .axi-btn beside it followed
|
|
743
|
+
--axi-radius-sm. A joined control is not hard, it is just impossible to
|
|
744
|
+
get right once per app.
|
|
745
|
+
|
|
746
|
+
The requirement the consumer stated is the one the rules below are built
|
|
747
|
+
to: it has to feel like one button on all styles. Not two buttons placed
|
|
748
|
+
carefully - one control that happens to have a seam. Everything here
|
|
749
|
+
follows from that, and the three surfaces disagree about enough
|
|
750
|
+
(--axi-border-control is 3px on axi and 1px on flat and glass,
|
|
751
|
+
--axi-shadow-control is a hard offset block on axi and a soft drop
|
|
752
|
+
elsewhere) that "looks joined" had to be true by construction rather than
|
|
753
|
+
by tuning. */
|
|
754
|
+
.axi-btn-split {
|
|
755
|
+
position: relative;
|
|
756
|
+
display: inline-flex;
|
|
757
|
+
/* stretch, not center: the two halves hold different content - a word and a
|
|
758
|
+
glyph - so content-sized heights drift apart by a pixel or two and the
|
|
759
|
+
seam stops being straight. */
|
|
760
|
+
align-items: stretch;
|
|
761
|
+
/* The wrapper moves and the halves do not, which is what makes the hover a
|
|
762
|
+
property of the control rather than of whichever half the cursor found. */
|
|
763
|
+
transition: transform .1s;
|
|
764
|
+
}
|
|
765
|
+
/* Corners: square at the seam, the surface's radius on the outside. Logical
|
|
766
|
+
properties rather than the `border-radius: 0 10px 10px 0` shorthand, so an
|
|
767
|
+
RTL consumer gets a control joined on the correct side without restating
|
|
768
|
+
anything - and so that each corner is set exactly once. The shorthand is
|
|
769
|
+
how the hand-written version ended up needing a comment explaining that
|
|
770
|
+
the rule above it had already squared both right corners. */
|
|
771
|
+
.axi-btn-split > .axi-btn { border-radius: 0; }
|
|
772
|
+
.axi-btn-split > .axi-btn:first-child {
|
|
773
|
+
border-start-start-radius: var(--axi-radius-sm);
|
|
774
|
+
border-end-start-radius: var(--axi-radius-sm);
|
|
775
|
+
}
|
|
776
|
+
.axi-btn-split > .axi-btn:last-child {
|
|
777
|
+
border-start-end-radius: var(--axi-radius-sm);
|
|
778
|
+
border-end-end-radius: var(--axi-radius-sm);
|
|
779
|
+
}
|
|
780
|
+
/* The seam is one shared edge, not a gap and not a drawn line. Pulling the
|
|
781
|
+
second half back by the control border width lands its left border exactly
|
|
782
|
+
on the first half's right border, so the two become a single stroke at the
|
|
783
|
+
language's own weight, in the language's own colour, on every surface and
|
|
784
|
+
under every ink. A 1px margin instead - which is what the hand-written
|
|
785
|
+
version used - puts two borders and a sliver of background between the
|
|
786
|
+
halves: 7px of edge on the default surface where the border is 3px, which
|
|
787
|
+
is why that control read as two buttons touching.
|
|
788
|
+
|
|
789
|
+
Negative margin and not `border-inline-start: 0`, because dropping the
|
|
790
|
+
border would make the toggle one border-width shorter than its sibling and
|
|
791
|
+
the stretch above would have to fight it. */
|
|
792
|
+
.axi-btn-split > .axi-btn + .axi-btn {
|
|
793
|
+
margin-inline-start: calc(var(--axi-border-control) * -1);
|
|
794
|
+
}
|
|
795
|
+
/* A filled half needs the seam drawn in the ink that sits ON the fill, not in
|
|
796
|
+
the line that sits around it. --axi-ink-line is opaque near-black on the
|
|
797
|
+
main theme, where it reads as a stroke against anything; on flat and glass
|
|
798
|
+
it is a light translucent edge - rgba(255,255,255,.10) and
|
|
799
|
+
rgba(180,200,255,.16) - chosen to catch light against a dark surface, which
|
|
800
|
+
is every other place the language draws it. Laid across two bright accent
|
|
801
|
+
fields it is not faint, it is the wrong direction: a lightener on a light
|
|
802
|
+
fill can never resolve into an edge, so the shared stroke was there and
|
|
803
|
+
simply could not be seen, and the control read as one undivided lozenge.
|
|
804
|
+
|
|
805
|
+
--axi-accent-ink is the language's answer to "what reads on top of the
|
|
806
|
+
accent" - it is what the label is already drawn in - and it is near-black on
|
|
807
|
+
all three surfaces. On the main theme it is literally --axi-ink-line, so
|
|
808
|
+
this changes nothing there and the 3px seam is the same stroke it was.
|
|
809
|
+
|
|
810
|
+
Either half being filled is enough: the stroke you see is the second half's
|
|
811
|
+
border, and a dark hairline reads against the plain fill on the other side
|
|
812
|
+
of it just as well. */
|
|
813
|
+
.axi-btn-split > .axi-btn--primary + .axi-btn,
|
|
814
|
+
.axi-btn-split > .axi-btn + .axi-btn--primary {
|
|
815
|
+
border-inline-start-color: var(--axi-accent-ink);
|
|
816
|
+
}
|
|
817
|
+
/* Neither half casts a shadow across the seam. The surfaces that spell their
|
|
818
|
+
relief as a soft drop give every control a blur that reaches outward on all
|
|
819
|
+
four sides - glass's is `0 6px 16px`, so it extends roughly 8px sideways -
|
|
820
|
+
and a half of a joined control has a sibling 8px away rather than the page.
|
|
821
|
+
The toggle paints after the label half, so its leftward bleed lands on top
|
|
822
|
+
of its neighbour as a dark vertical band just inside the seam, which is the
|
|
823
|
+
second way this control could read as two buttons rather than one. The
|
|
824
|
+
label half bleeds the other way for the same reason, and shows wherever the
|
|
825
|
+
toggle's fill is translucent - which on glass is every tone but the ink'd
|
|
826
|
+
ones.
|
|
827
|
+
|
|
828
|
+
So each half clips its own shadow flush with the edge it shares, and leaves
|
|
829
|
+
the other three sides alone to drop as far as the surface says. The clip is
|
|
830
|
+
a no-op on the main theme, whose block is a hard offset that only ever
|
|
831
|
+
travels right and down, and nearly one on flat; it does its work exactly
|
|
832
|
+
where the blur is wide enough to cross.
|
|
833
|
+
|
|
834
|
+
-100% is the box's own width or height, which is always more slack than a
|
|
835
|
+
control-sized blur needs, and stays right if a surface ever spells a bigger
|
|
836
|
+
one - a literal here would be a number to maintain.
|
|
837
|
+
|
|
838
|
+
`inset()` is physical, so RTL restates the pair rather than inheriting a
|
|
839
|
+
clip on the wrong edges; this is the one place in the component where the
|
|
840
|
+
logical properties above cannot carry the direction.
|
|
841
|
+
|
|
842
|
+
Not while focused: the focus ring is an `outline` drawn 2px outside the box,
|
|
843
|
+
so a clip flush with that edge would cut it in half on the seam side. A
|
|
844
|
+
keyboard user seeing a complete ring is worth more than the band. */
|
|
845
|
+
.axi-btn-split > .axi-btn:where(:not(:focus-visible)):first-child { clip-path: inset(-100% 0 -100% -100%); }
|
|
846
|
+
.axi-btn-split > .axi-btn:where(:not(:focus-visible)):last-child { clip-path: inset(-100% -100% -100% 0); }
|
|
847
|
+
.axi-btn-split:dir(rtl) > .axi-btn:where(:not(:focus-visible)):first-child { clip-path: inset(-100% -100% -100% 0); }
|
|
848
|
+
.axi-btn-split:dir(rtl) > .axi-btn:where(:not(:focus-visible)):last-child { clip-path: inset(-100% 0 -100% -100%); }
|
|
849
|
+
/* Hover is the whole control's. The generic .axi-btn hover translates the
|
|
850
|
+
button and deepens its block; left alone, that moves one half of a joined
|
|
851
|
+
control two pixels up and away from the other and tears the seam open -
|
|
852
|
+
the most visible way this component could fail at being one button.
|
|
853
|
+
So: the translate moves to the wrapper, and both halves take the hover
|
|
854
|
+
block whichever one the cursor is over.
|
|
855
|
+
|
|
856
|
+
What stays per-half is colour. The generic hover's dim-to-plain brighten
|
|
857
|
+
still lands only on the half under the cursor, which is the whole reason a
|
|
858
|
+
split button has two targets - the control lifts as one, and the reader can
|
|
859
|
+
still see which action they are about to take.
|
|
860
|
+
|
|
861
|
+
Specificity: these two rules weigh the same as `.axi-btn--primary:hover`
|
|
862
|
+
and win on source order, which is the ordering this section depends on -
|
|
863
|
+
it must stay below the button section it adjusts. The `:where()` keeps the
|
|
864
|
+
wrapper's hover from spending a point, so the ink layer still out-ranks
|
|
865
|
+
everything here, per rule 6. */
|
|
866
|
+
.axi-btn-split:where(:hover) { transform: translate(-2px, -2px); }
|
|
867
|
+
.axi-btn-split > .axi-btn:where(:hover) { transform: none; }
|
|
868
|
+
.axi-btn-split:where(:hover) > .axi-btn { box-shadow: var(--axi-shadow-control); }
|
|
869
|
+
.axi-btn-split:where(:hover) > .axi-btn--primary { box-shadow: var(--axi-shadow-control-hover); }
|
|
870
|
+
/* The disclosure half. Vertical padding is zero because the stretch above
|
|
871
|
+
already gave it the label half's height, and 20px sides around a single
|
|
872
|
+
chevron is the wide-rectangle failure --icon exists to fix. The gap is for
|
|
873
|
+
the consumers that put a status dot beside the chevron. */
|
|
874
|
+
.axi-btn-split__toggle { --axi-btn-pad: 0 11px; gap: 5px; }
|
|
875
|
+
|
|
730
876
|
/* ---------- pill ---------- */
|
|
731
877
|
/* A filter toggle. Its pressed state fills with whatever colour the consumer
|
|
732
878
|
has put on it, so the control reads as the thing it filters to rather than
|
|
@@ -2516,6 +2662,12 @@ textarea.axi-input {
|
|
|
2516
2662
|
display: flex; align-items: center; gap: 12px;
|
|
2517
2663
|
height: 38px; padding-left: 14px;
|
|
2518
2664
|
background: var(--axi-ground-deep);
|
|
2665
|
+
/* The chrome takes the surface filter for the same reason a panel does: on a
|
|
2666
|
+
theme that makes its surfaces translucent, the one strip a reader never
|
|
2667
|
+
scrolls away is the one that has to agree. --axi-surface-filter is none on
|
|
2668
|
+
every opaque surface, so this is inert there - the same inert-by-default
|
|
2669
|
+
shape the panels already use. */
|
|
2670
|
+
backdrop-filter: var(--axi-surface-filter);
|
|
2519
2671
|
border-bottom: var(--axi-border-control) solid var(--axi-ink-line);
|
|
2520
2672
|
font: var(--axi-t-micro);
|
|
2521
2673
|
letter-spacing: var(--axi-ls-micro);
|
|
@@ -2540,6 +2692,13 @@ textarea.axi-input {
|
|
|
2540
2692
|
/* Close is the one destructive control in the chrome, and the one place a
|
|
2541
2693
|
status ink belongs in a titlebar. */
|
|
2542
2694
|
.axi-titlebar__btns :where(button:last-child:hover) { background: var(--axi-danger); color: var(--axi-ink-on-fill); }
|
|
2695
|
+
/* The page-wide ring sits 2px OUTSIDE its element, which a window button
|
|
2696
|
+
cannot afford: it is flush to the top of the frame and flush to its
|
|
2697
|
+
neighbours, so an outward ring spills past the window edge and reads as a
|
|
2698
|
+
loud accent box across the corner of the chrome. Pull it inward, the same
|
|
2699
|
+
way a picker option and a select option already do inside their own tight
|
|
2700
|
+
containers. */
|
|
2701
|
+
.axi-titlebar__btns :where(button:focus-visible) { outline-offset: -3px; }
|
|
2543
2702
|
/* The scrolling region under the chrome. */
|
|
2544
2703
|
.axi-window__body { flex: 1; overflow-y: auto; }
|
|
2545
2704
|
|
package/dist/themes/flat.css
CHANGED
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
--axi-text-dim: #8b929e;
|
|
18
18
|
--axi-text-faint: #5d6577;
|
|
19
19
|
--axi-scrim: rgba(4, 5, 8, .72);
|
|
20
|
+
--axi-offset-panel: 0;
|
|
21
|
+
--axi-offset-control: 0;
|
|
22
|
+
--axi-offset-panel-hover: 0;
|
|
23
|
+
--axi-offset-control-hover: 0;
|
|
20
24
|
--axi-shadow-panel: 0 2px 8px rgba(0, 0, 0, .4), 0 1px 3px rgba(0, 0, 0, .3), inset 0 1px 0 rgba(255, 255, 255, .05);
|
|
21
25
|
--axi-shadow-control: 0 1px 4px rgba(0, 0, 0, .3), inset 0 1px 0 rgba(255, 255, 255, .045);
|
|
22
26
|
--axi-shadow-panel-hover: 0 6px 18px rgba(0, 0, 0, .5), 0 2px 5px rgba(0, 0, 0, .35), inset 0 1px 0 rgba(255, 255, 255, .07);
|
package/dist/themes/glass.css
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
--axi-surface-filter: blur(18px) saturate(140%);
|
|
14
14
|
--axi-ink-line: rgba(180, 200, 255, .16);
|
|
15
15
|
--axi-rule: rgba(180, 200, 255, .14);
|
|
16
|
-
--axi-ground-deep:
|
|
16
|
+
--axi-ground-deep: rgba(12, 14, 18, .72);
|
|
17
17
|
--axi-well-fill: rgba(6, 8, 14, .32);
|
|
18
18
|
--axi-accent-ink: #0c0e12;
|
|
19
19
|
--axi-ink-on-fill: #0c0e12;
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
--axi-text-dim: #b3bdd0;
|
|
22
22
|
--axi-text-faint: #7f8aa0;
|
|
23
23
|
--axi-scrim: rgba(4, 6, 12, .66);
|
|
24
|
+
--axi-offset-panel: 0;
|
|
25
|
+
--axi-offset-control: 0;
|
|
26
|
+
--axi-offset-panel-hover: 0;
|
|
27
|
+
--axi-offset-control-hover: 0;
|
|
24
28
|
--axi-shadow-panel: 0 18px 44px rgba(0, 0, 0, .55), inset 0 1px 0 rgba(255, 255, 255, .14);
|
|
25
29
|
--axi-shadow-control: 0 6px 16px rgba(0, 0, 0, .45), inset 0 1px 0 rgba(255, 255, 255, .12);
|
|
26
30
|
--axi-shadow-panel-hover: 0 26px 60px rgba(0, 0, 0, .6), inset 0 1px 0 rgba(255, 255, 255, .2);
|
package/docs/RULES.md
CHANGED
|
@@ -830,10 +830,19 @@ What is **not** a theme's to restate, and each for its own reason:
|
|
|
830
830
|
companions are not on this list: `--axi-accent-ink` and `--axi-ink-on-fill`
|
|
831
831
|
are the colour a word is written in when it sits on a fill, and a theme that
|
|
832
832
|
lightens the outline has to be able to hold them dark.
|
|
833
|
-
- **The offset scale.** `--axi-offset-*` is what the
|
|
834
|
-
composed *from
|
|
835
|
-
|
|
836
|
-
|
|
833
|
+
- **The offset scale, except to switch it off.** `--axi-offset-*` is what the
|
|
834
|
+
main theme's block is composed *from*, and retuning it underneath a restated
|
|
835
|
+
`--axi-shadow-*` would leave the two spellings of "the block" disagreeing.
|
|
836
|
+
A theme may set the offsets to `0` and may do nothing else to them. Zero is
|
|
837
|
+
not a retune — it is the same sentence the restated `--axi-shadow-*` is
|
|
838
|
+
already saying. This started out as a flat prohibition and shipped a bug:
|
|
839
|
+
the tokens are public, consumers compose blocks from them directly rather
|
|
840
|
+
than asking for `--axi-shadow-panel`, and a theme whose relief is a soft drop
|
|
841
|
+
was leaving a 6px near-black step standing behind every one of those cards.
|
|
842
|
+
On a square corner it read as a stray backdrop; on a rounded one it read as
|
|
843
|
+
broken rounding. A consumer still composing from the offsets now gets no
|
|
844
|
+
relief on a theme rather than the wrong one, which is a visible prompt to ask
|
|
845
|
+
for the composed block instead.
|
|
837
846
|
- **The measure.** `--axi-page`, `--axi-gutter`. Where the text wraps is not a
|
|
838
847
|
look, and moving it would reflow every page rather than repaint it.
|
|
839
848
|
- **The type.** `--axi-sans`, `--axi-mono`, `--axi-t-*`, `--axi-ls-*`. The same
|
package/package.json
CHANGED
package/src/primitives.css
CHANGED
|
@@ -323,6 +323,152 @@ button.axi-panel--tile:hover,
|
|
|
323
323
|
.axi-btn--xs { --axi-btn-pad: 5px 9px; --axi-btn-size: 11px; }
|
|
324
324
|
.axi-btn--icon { --axi-btn-pad: 7px; }
|
|
325
325
|
|
|
326
|
+
/* ---------- split button ---------- */
|
|
327
|
+
/* One control with two targets: a primary action and a disclosure half that
|
|
328
|
+
opens a menu of its variants. The name is `.axi-btn-split` and not
|
|
329
|
+
`.axi-split` because that one is already the split *pane* in
|
|
330
|
+
src/layout.css - a pane and a button are not the same noun, and the
|
|
331
|
+
language only gets one of them per name.
|
|
332
|
+
|
|
333
|
+
The whole component exists because a consumer hand-wrote it and got the
|
|
334
|
+
radius wrong, which was not carelessness: a split button is the one place
|
|
335
|
+
where a corner is sometimes square and sometimes the surface's, so it is
|
|
336
|
+
the one place where a literal gets written. That consumer's control was
|
|
337
|
+
rounded at 10px on all three surfaces, correct on glass and wrong on the
|
|
338
|
+
other two, while every ordinary .axi-btn beside it followed
|
|
339
|
+
--axi-radius-sm. A joined control is not hard, it is just impossible to
|
|
340
|
+
get right once per app.
|
|
341
|
+
|
|
342
|
+
The requirement the consumer stated is the one the rules below are built
|
|
343
|
+
to: it has to feel like one button on all styles. Not two buttons placed
|
|
344
|
+
carefully - one control that happens to have a seam. Everything here
|
|
345
|
+
follows from that, and the three surfaces disagree about enough
|
|
346
|
+
(--axi-border-control is 3px on axi and 1px on flat and glass,
|
|
347
|
+
--axi-shadow-control is a hard offset block on axi and a soft drop
|
|
348
|
+
elsewhere) that "looks joined" had to be true by construction rather than
|
|
349
|
+
by tuning. */
|
|
350
|
+
.axi-btn-split {
|
|
351
|
+
position: relative;
|
|
352
|
+
display: inline-flex;
|
|
353
|
+
/* stretch, not center: the two halves hold different content - a word and a
|
|
354
|
+
glyph - so content-sized heights drift apart by a pixel or two and the
|
|
355
|
+
seam stops being straight. */
|
|
356
|
+
align-items: stretch;
|
|
357
|
+
/* The wrapper moves and the halves do not, which is what makes the hover a
|
|
358
|
+
property of the control rather than of whichever half the cursor found. */
|
|
359
|
+
transition: transform .1s;
|
|
360
|
+
}
|
|
361
|
+
/* Corners: square at the seam, the surface's radius on the outside. Logical
|
|
362
|
+
properties rather than the `border-radius: 0 10px 10px 0` shorthand, so an
|
|
363
|
+
RTL consumer gets a control joined on the correct side without restating
|
|
364
|
+
anything - and so that each corner is set exactly once. The shorthand is
|
|
365
|
+
how the hand-written version ended up needing a comment explaining that
|
|
366
|
+
the rule above it had already squared both right corners. */
|
|
367
|
+
.axi-btn-split > .axi-btn { border-radius: 0; }
|
|
368
|
+
.axi-btn-split > .axi-btn:first-child {
|
|
369
|
+
border-start-start-radius: var(--axi-radius-sm);
|
|
370
|
+
border-end-start-radius: var(--axi-radius-sm);
|
|
371
|
+
}
|
|
372
|
+
.axi-btn-split > .axi-btn:last-child {
|
|
373
|
+
border-start-end-radius: var(--axi-radius-sm);
|
|
374
|
+
border-end-end-radius: var(--axi-radius-sm);
|
|
375
|
+
}
|
|
376
|
+
/* The seam is one shared edge, not a gap and not a drawn line. Pulling the
|
|
377
|
+
second half back by the control border width lands its left border exactly
|
|
378
|
+
on the first half's right border, so the two become a single stroke at the
|
|
379
|
+
language's own weight, in the language's own colour, on every surface and
|
|
380
|
+
under every ink. A 1px margin instead - which is what the hand-written
|
|
381
|
+
version used - puts two borders and a sliver of background between the
|
|
382
|
+
halves: 7px of edge on the default surface where the border is 3px, which
|
|
383
|
+
is why that control read as two buttons touching.
|
|
384
|
+
|
|
385
|
+
Negative margin and not `border-inline-start: 0`, because dropping the
|
|
386
|
+
border would make the toggle one border-width shorter than its sibling and
|
|
387
|
+
the stretch above would have to fight it. */
|
|
388
|
+
.axi-btn-split > .axi-btn + .axi-btn {
|
|
389
|
+
margin-inline-start: calc(var(--axi-border-control) * -1);
|
|
390
|
+
}
|
|
391
|
+
/* A filled half needs the seam drawn in the ink that sits ON the fill, not in
|
|
392
|
+
the line that sits around it. --axi-ink-line is opaque near-black on the
|
|
393
|
+
main theme, where it reads as a stroke against anything; on flat and glass
|
|
394
|
+
it is a light translucent edge - rgba(255,255,255,.10) and
|
|
395
|
+
rgba(180,200,255,.16) - chosen to catch light against a dark surface, which
|
|
396
|
+
is every other place the language draws it. Laid across two bright accent
|
|
397
|
+
fields it is not faint, it is the wrong direction: a lightener on a light
|
|
398
|
+
fill can never resolve into an edge, so the shared stroke was there and
|
|
399
|
+
simply could not be seen, and the control read as one undivided lozenge.
|
|
400
|
+
|
|
401
|
+
--axi-accent-ink is the language's answer to "what reads on top of the
|
|
402
|
+
accent" - it is what the label is already drawn in - and it is near-black on
|
|
403
|
+
all three surfaces. On the main theme it is literally --axi-ink-line, so
|
|
404
|
+
this changes nothing there and the 3px seam is the same stroke it was.
|
|
405
|
+
|
|
406
|
+
Either half being filled is enough: the stroke you see is the second half's
|
|
407
|
+
border, and a dark hairline reads against the plain fill on the other side
|
|
408
|
+
of it just as well. */
|
|
409
|
+
.axi-btn-split > .axi-btn--primary + .axi-btn,
|
|
410
|
+
.axi-btn-split > .axi-btn + .axi-btn--primary {
|
|
411
|
+
border-inline-start-color: var(--axi-accent-ink);
|
|
412
|
+
}
|
|
413
|
+
/* Neither half casts a shadow across the seam. The surfaces that spell their
|
|
414
|
+
relief as a soft drop give every control a blur that reaches outward on all
|
|
415
|
+
four sides - glass's is `0 6px 16px`, so it extends roughly 8px sideways -
|
|
416
|
+
and a half of a joined control has a sibling 8px away rather than the page.
|
|
417
|
+
The toggle paints after the label half, so its leftward bleed lands on top
|
|
418
|
+
of its neighbour as a dark vertical band just inside the seam, which is the
|
|
419
|
+
second way this control could read as two buttons rather than one. The
|
|
420
|
+
label half bleeds the other way for the same reason, and shows wherever the
|
|
421
|
+
toggle's fill is translucent - which on glass is every tone but the ink'd
|
|
422
|
+
ones.
|
|
423
|
+
|
|
424
|
+
So each half clips its own shadow flush with the edge it shares, and leaves
|
|
425
|
+
the other three sides alone to drop as far as the surface says. The clip is
|
|
426
|
+
a no-op on the main theme, whose block is a hard offset that only ever
|
|
427
|
+
travels right and down, and nearly one on flat; it does its work exactly
|
|
428
|
+
where the blur is wide enough to cross.
|
|
429
|
+
|
|
430
|
+
-100% is the box's own width or height, which is always more slack than a
|
|
431
|
+
control-sized blur needs, and stays right if a surface ever spells a bigger
|
|
432
|
+
one - a literal here would be a number to maintain.
|
|
433
|
+
|
|
434
|
+
`inset()` is physical, so RTL restates the pair rather than inheriting a
|
|
435
|
+
clip on the wrong edges; this is the one place in the component where the
|
|
436
|
+
logical properties above cannot carry the direction.
|
|
437
|
+
|
|
438
|
+
Not while focused: the focus ring is an `outline` drawn 2px outside the box,
|
|
439
|
+
so a clip flush with that edge would cut it in half on the seam side. A
|
|
440
|
+
keyboard user seeing a complete ring is worth more than the band. */
|
|
441
|
+
.axi-btn-split > .axi-btn:where(:not(:focus-visible)):first-child { clip-path: inset(-100% 0 -100% -100%); }
|
|
442
|
+
.axi-btn-split > .axi-btn:where(:not(:focus-visible)):last-child { clip-path: inset(-100% -100% -100% 0); }
|
|
443
|
+
.axi-btn-split:dir(rtl) > .axi-btn:where(:not(:focus-visible)):first-child { clip-path: inset(-100% -100% -100% 0); }
|
|
444
|
+
.axi-btn-split:dir(rtl) > .axi-btn:where(:not(:focus-visible)):last-child { clip-path: inset(-100% 0 -100% -100%); }
|
|
445
|
+
/* Hover is the whole control's. The generic .axi-btn hover translates the
|
|
446
|
+
button and deepens its block; left alone, that moves one half of a joined
|
|
447
|
+
control two pixels up and away from the other and tears the seam open -
|
|
448
|
+
the most visible way this component could fail at being one button.
|
|
449
|
+
So: the translate moves to the wrapper, and both halves take the hover
|
|
450
|
+
block whichever one the cursor is over.
|
|
451
|
+
|
|
452
|
+
What stays per-half is colour. The generic hover's dim-to-plain brighten
|
|
453
|
+
still lands only on the half under the cursor, which is the whole reason a
|
|
454
|
+
split button has two targets - the control lifts as one, and the reader can
|
|
455
|
+
still see which action they are about to take.
|
|
456
|
+
|
|
457
|
+
Specificity: these two rules weigh the same as `.axi-btn--primary:hover`
|
|
458
|
+
and win on source order, which is the ordering this section depends on -
|
|
459
|
+
it must stay below the button section it adjusts. The `:where()` keeps the
|
|
460
|
+
wrapper's hover from spending a point, so the ink layer still out-ranks
|
|
461
|
+
everything here, per rule 6. */
|
|
462
|
+
.axi-btn-split:where(:hover) { transform: translate(-2px, -2px); }
|
|
463
|
+
.axi-btn-split > .axi-btn:where(:hover) { transform: none; }
|
|
464
|
+
.axi-btn-split:where(:hover) > .axi-btn { box-shadow: var(--axi-shadow-control); }
|
|
465
|
+
.axi-btn-split:where(:hover) > .axi-btn--primary { box-shadow: var(--axi-shadow-control-hover); }
|
|
466
|
+
/* The disclosure half. Vertical padding is zero because the stretch above
|
|
467
|
+
already gave it the label half's height, and 20px sides around a single
|
|
468
|
+
chevron is the wide-rectangle failure --icon exists to fix. The gap is for
|
|
469
|
+
the consumers that put a status dot beside the chevron. */
|
|
470
|
+
.axi-btn-split__toggle { --axi-btn-pad: 0 11px; gap: 5px; }
|
|
471
|
+
|
|
326
472
|
/* ---------- pill ---------- */
|
|
327
473
|
/* A filter toggle. Its pressed state fills with whatever colour the consumer
|
|
328
474
|
has put on it, so the control reads as the thing it filters to rather than
|
package/src/shells.css
CHANGED
|
@@ -947,6 +947,12 @@
|
|
|
947
947
|
display: flex; align-items: center; gap: 12px;
|
|
948
948
|
height: 38px; padding-left: 14px;
|
|
949
949
|
background: var(--axi-ground-deep);
|
|
950
|
+
/* The chrome takes the surface filter for the same reason a panel does: on a
|
|
951
|
+
theme that makes its surfaces translucent, the one strip a reader never
|
|
952
|
+
scrolls away is the one that has to agree. --axi-surface-filter is none on
|
|
953
|
+
every opaque surface, so this is inert there - the same inert-by-default
|
|
954
|
+
shape the panels already use. */
|
|
955
|
+
backdrop-filter: var(--axi-surface-filter);
|
|
950
956
|
border-bottom: var(--axi-border-control) solid var(--axi-ink-line);
|
|
951
957
|
font: var(--axi-t-micro);
|
|
952
958
|
letter-spacing: var(--axi-ls-micro);
|
|
@@ -971,6 +977,13 @@
|
|
|
971
977
|
/* Close is the one destructive control in the chrome, and the one place a
|
|
972
978
|
status ink belongs in a titlebar. */
|
|
973
979
|
.axi-titlebar__btns :where(button:last-child:hover) { background: var(--axi-danger); color: var(--axi-ink-on-fill); }
|
|
980
|
+
/* The page-wide ring sits 2px OUTSIDE its element, which a window button
|
|
981
|
+
cannot afford: it is flush to the top of the frame and flush to its
|
|
982
|
+
neighbours, so an outward ring spills past the window edge and reads as a
|
|
983
|
+
loud accent box across the corner of the chrome. Pull it inward, the same
|
|
984
|
+
way a picker option and a select option already do inside their own tight
|
|
985
|
+
containers. */
|
|
986
|
+
.axi-titlebar__btns :where(button:focus-visible) { outline-offset: -3px; }
|
|
974
987
|
/* The scrolling region under the chrome. */
|
|
975
988
|
.axi-window__body { flex: 1; overflow-y: auto; }
|
|
976
989
|
|