@apliteni/apliteni-ui 0.128.0 → 0.129.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/package.json +1 -1
- package/react/dist/index.css +48 -46
- package/react/dist/index.js +52 -63
- package/src/assets/icons.js +5 -0
- package/src/components/drawer.js +5 -2
- package/src/components/overlay.js +9 -5
- package/src/components/shell.js +75 -9
- package/src/styles/layout.css +50 -78
- package/src/styles/topbar.css +9 -0
package/package.json
CHANGED
package/react/dist/index.css
CHANGED
|
@@ -852,7 +852,7 @@
|
|
|
852
852
|
.rx-toast-stack {
|
|
853
853
|
position: fixed;
|
|
854
854
|
right: var(--space-4);
|
|
855
|
-
bottom:
|
|
855
|
+
bottom: var(--space-4);
|
|
856
856
|
max-width: calc(100% - 2 * var(--space-4));
|
|
857
857
|
z-index: var(--z-toast);
|
|
858
858
|
pointer-events: none;
|
|
@@ -869,9 +869,6 @@
|
|
|
869
869
|
}
|
|
870
870
|
|
|
871
871
|
/* src/AppShell.css */
|
|
872
|
-
.ui-react-app__bottom {
|
|
873
|
-
display: none;
|
|
874
|
-
}
|
|
875
872
|
.ui-react-app__actions {
|
|
876
873
|
display: flex;
|
|
877
874
|
flex-wrap: wrap;
|
|
@@ -882,66 +879,71 @@
|
|
|
882
879
|
display: inline-flex;
|
|
883
880
|
opacity: 1;
|
|
884
881
|
}
|
|
885
|
-
.ui-react-app__bottom .ui-nav__item {
|
|
886
|
-
min-height: 48px;
|
|
887
|
-
}
|
|
888
882
|
.ui-react-app:not(:has(> .ui-app__rail)) {
|
|
889
883
|
grid-template-columns: minmax(0, 1fr);
|
|
890
884
|
}
|
|
891
|
-
.ui-react-app .ui-app__bar > .ui-app__brand {
|
|
885
|
+
.ui-react-app .ui-app__bar > .ui-app__brand:not(.ui-app__brand--band) {
|
|
892
886
|
flex: none;
|
|
893
887
|
margin-inline-end: var(--space-2);
|
|
894
888
|
}
|
|
889
|
+
.ui-react-app__menu .ui-nav--side,
|
|
890
|
+
.ui-react-app__menu .ui-nav--side > * {
|
|
891
|
+
width: 100%;
|
|
892
|
+
}
|
|
893
|
+
.ui-react-app__menu .ui-nav__item {
|
|
894
|
+
min-height: 44px;
|
|
895
|
+
}
|
|
896
|
+
:where(:root[data-theme=light]) .ui-react-app__menu .ui-nav__item:not(.is-danger):not(.is-active):hover {
|
|
897
|
+
background: color-mix(in srgb, var(--surface-3) 50%, transparent);
|
|
898
|
+
}
|
|
899
|
+
:where(:root[data-theme=light]) .ui-react-app__menu .ui-nav__item.is-active {
|
|
900
|
+
background: var(--surface-3);
|
|
901
|
+
}
|
|
902
|
+
.ui-react-app__band-control {
|
|
903
|
+
display: contents;
|
|
904
|
+
}
|
|
895
905
|
@media (max-width: 560px) {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
:root:has(.ui-react-app) {
|
|
900
|
-
scroll-padding-bottom: calc(var(--space-5) + var(--ui-app-bottom-clearance, 0px));
|
|
901
|
-
}
|
|
902
|
-
.ui-react-app {
|
|
903
|
-
display: block;
|
|
906
|
+
.ui-react-app .ui-app__bar {
|
|
907
|
+
gap: var(--space-1);
|
|
908
|
+
padding-inline: var(--space-2);
|
|
904
909
|
}
|
|
905
|
-
.ui-react-app
|
|
910
|
+
.ui-react-app .ui-app__bar-gap {
|
|
906
911
|
display: none;
|
|
907
912
|
}
|
|
908
|
-
|
|
909
|
-
padding-bottom: var(--ui-app-bottom-clearance, 0px);
|
|
910
|
-
}
|
|
911
|
-
.ui-react-app .ui-app__bar {
|
|
912
|
-
padding-inline: var(--space-4);
|
|
913
|
-
}
|
|
914
|
-
.ui-react-app .ui-app__bar > .ui-app__brand {
|
|
913
|
+
.ui-react-app .ui-app__search-ic {
|
|
915
914
|
display: none;
|
|
916
915
|
}
|
|
917
|
-
.ui-react-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
background: var(--bg);
|
|
924
|
-
border-top: 1px solid var(--border);
|
|
925
|
-
}
|
|
926
|
-
.ui-react-app__bottom .ui-nav__list {
|
|
916
|
+
.ui-react-app .ui-app__search {
|
|
917
|
+
flex: 1 1 auto;
|
|
918
|
+
min-width: 56px;
|
|
919
|
+
padding-inline: var(--space-2);
|
|
920
|
+
}
|
|
921
|
+
.ui-react-app__band-control {
|
|
927
922
|
display: flex;
|
|
928
|
-
flex
|
|
923
|
+
flex: 0 1 auto;
|
|
924
|
+
min-width: 56px;
|
|
925
|
+
max-width: min(90px, calc(100vw - 261px));
|
|
929
926
|
}
|
|
930
|
-
.ui-react-
|
|
931
|
-
flex: 1;
|
|
927
|
+
.ui-react-app__band-control > .ui-btn {
|
|
932
928
|
min-width: 0;
|
|
929
|
+
min-height: 34px;
|
|
930
|
+
max-width: 100%;
|
|
933
931
|
}
|
|
934
|
-
.ui-react-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
932
|
+
.ui-react-app .ui-app__bar .toggle {
|
|
933
|
+
margin-inline-start: 1px;
|
|
934
|
+
}
|
|
935
|
+
.ui-react-app .ui-app__user--bar {
|
|
936
|
+
margin-inline-start: 2px;
|
|
937
|
+
}
|
|
938
|
+
.ui-react-app .ui-app__user-trigger {
|
|
939
|
+
min-width: 44px;
|
|
939
940
|
}
|
|
940
|
-
.ui-react-
|
|
941
|
-
|
|
942
|
-
|
|
941
|
+
.ui-react-app__band-control > .ui-btn .ui-btn__label {
|
|
942
|
+
overflow: hidden;
|
|
943
|
+
text-overflow: ellipsis;
|
|
944
|
+
white-space: nowrap;
|
|
943
945
|
}
|
|
944
|
-
.ui-react-
|
|
946
|
+
.ui-react-app .ui-app__bar > .ui-app__brand:not(.ui-app__brand--band) {
|
|
945
947
|
display: none;
|
|
946
948
|
}
|
|
947
949
|
}
|
package/react/dist/index.js
CHANGED
|
@@ -3553,7 +3553,7 @@ function Toast({ children }) {
|
|
|
3553
3553
|
}
|
|
3554
3554
|
|
|
3555
3555
|
// src/AppShell.tsx
|
|
3556
|
-
import { useEffect as useEffect14, useId as useId12, useState as useState14 } from "react";
|
|
3556
|
+
import { useEffect as useEffect14, useId as useId12, useRef as useRef19, useState as useState14 } from "react";
|
|
3557
3557
|
import { paletteHotkey, prism, railCollapsed } from "@apliteni/apliteni-ui";
|
|
3558
3558
|
|
|
3559
3559
|
// src/SidebarNav.tsx
|
|
@@ -3751,16 +3751,26 @@ function AppShell({
|
|
|
3751
3751
|
const uid = useId12();
|
|
3752
3752
|
const [collapsed, setCollapsed] = useState14(defaultCollapsed);
|
|
3753
3753
|
const [search, setSearch] = useState14(false);
|
|
3754
|
-
const [
|
|
3754
|
+
const [menu, setMenu] = useState14(false);
|
|
3755
|
+
const menuButton = useRef19(null);
|
|
3755
3756
|
const current = path(pathname);
|
|
3756
3757
|
const active = sections.filter((section) => current === path(section.href) || path(section.href) !== "/" && current.startsWith(`${path(section.href)}/`)).sort((a, b) => path(b.href).length - path(a.href).length)[0];
|
|
3757
3758
|
useEffect14(() => {
|
|
3758
3759
|
setCollapsed(railCollapsed() ?? defaultCollapsed);
|
|
3759
3760
|
}, [defaultCollapsed]);
|
|
3760
3761
|
useEffect14(() => {
|
|
3761
|
-
|
|
3762
|
+
setMenu(false);
|
|
3762
3763
|
setSearch(false);
|
|
3763
3764
|
}, [pathname]);
|
|
3765
|
+
useEffect14(() => {
|
|
3766
|
+
if (!menu) return;
|
|
3767
|
+
const grown = () => {
|
|
3768
|
+
const button = menuButton.current;
|
|
3769
|
+
if (button && getComputedStyle(button).display === "none") setMenu(false);
|
|
3770
|
+
};
|
|
3771
|
+
window.addEventListener("resize", grown);
|
|
3772
|
+
return () => window.removeEventListener("resize", grown);
|
|
3773
|
+
}, [menu]);
|
|
3764
3774
|
useEffect14(() => {
|
|
3765
3775
|
const hotkey = (event) => {
|
|
3766
3776
|
if (!(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey || event.key.toLowerCase() !== "k") return;
|
|
@@ -3780,36 +3790,17 @@ function AppShell({
|
|
|
3780
3790
|
} catch {
|
|
3781
3791
|
}
|
|
3782
3792
|
};
|
|
3783
|
-
const
|
|
3784
|
-
const linkProps = {
|
|
3785
|
-
href: section.href,
|
|
3786
|
-
className: `ui-nav__item${section === active ? " is-active" : ""}`,
|
|
3787
|
-
// guidelines/going-back.md, section-lit: the same row, so the same reading as
|
|
3788
|
-
// the rail's. "page" here would announce the list as the page the reader is on.
|
|
3789
|
-
"aria-current": section === active ? back ? "true" : "page" : void 0,
|
|
3790
|
-
"aria-label": `${section.label}${section.count == null ? "" : ` ${section.count}`}`,
|
|
3791
|
-
onClick: (event) => {
|
|
3792
|
-
if (!event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) setMore(false);
|
|
3793
|
-
},
|
|
3794
|
-
children: /* @__PURE__ */ jsxs30(Fragment12, { children: [
|
|
3795
|
-
/* @__PURE__ */ jsx35("span", { className: "ui-nav__ic", children: /* @__PURE__ */ jsx35(Icon, { name: section.icon }) }),
|
|
3796
|
-
/* @__PURE__ */ jsx35("span", { className: "ui-nav__label", children: section.label }),
|
|
3797
|
-
section.count != null && /* @__PURE__ */ jsx35("span", { className: "ui-nav__badge is-neutral", children: section.count })
|
|
3798
|
-
] })
|
|
3799
|
-
};
|
|
3800
|
-
return /* @__PURE__ */ jsx35("li", { children: renderLink ? renderLink(section, linkProps) : /* @__PURE__ */ jsx35("a", { ...linkProps }) }, section.href);
|
|
3801
|
-
});
|
|
3802
|
-
const sidebar = (items, label) => /* @__PURE__ */ jsx35(
|
|
3793
|
+
const sidebar = (label) => /* @__PURE__ */ jsx35(
|
|
3803
3794
|
SidebarNav,
|
|
3804
3795
|
{
|
|
3805
3796
|
"aria-label": label,
|
|
3806
|
-
items:
|
|
3797
|
+
items: sections.map((section) => ({ ...section, id: section.href, badge: section.count })),
|
|
3807
3798
|
active: active?.href,
|
|
3808
3799
|
activeIs: back ? "section" : "page",
|
|
3809
3800
|
renderLink: (item, props) => {
|
|
3810
|
-
const section =
|
|
3801
|
+
const section = sections.find((section2) => section2.href === item.id);
|
|
3811
3802
|
const linkProps = { ...props, onClick: (event) => {
|
|
3812
|
-
if (!event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey)
|
|
3803
|
+
if (!event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) setMenu(false);
|
|
3813
3804
|
} };
|
|
3814
3805
|
return renderLink ? renderLink(section, linkProps) : /* @__PURE__ */ jsx35("a", { ...linkProps });
|
|
3815
3806
|
}
|
|
@@ -3817,8 +3808,6 @@ function AppShell({
|
|
|
3817
3808
|
);
|
|
3818
3809
|
const foldLabel = collapsed ? "Expand sidebar" : "Collapse sidebar";
|
|
3819
3810
|
const navigable = sections.length > 1 || Boolean(back);
|
|
3820
|
-
const visible = sections.length > 4 ? sections.slice(0, 3) : sections;
|
|
3821
|
-
const remaining = sections.slice(visible.length);
|
|
3822
3811
|
const initials = account.name.trim().split(/\s+/).map((part) => part[0]).slice(0, 2).join("");
|
|
3823
3812
|
const lockup = /* @__PURE__ */ jsxs30("a", { className: "ui-app__brand", href: brandHref, "aria-label": word, children: [
|
|
3824
3813
|
brand ?? /* @__PURE__ */ jsx35("i", { "aria-hidden": "true", className: "ui-react-app__mark", dangerouslySetInnerHTML: { __html: prism(uid, 24) } }),
|
|
@@ -3827,7 +3816,7 @@ function AppShell({
|
|
|
3827
3816
|
return /* @__PURE__ */ jsxs30("div", { className: `ui-app ui-app--topbar ui-react-app${collapsed ? " is-collapsed" : ""}`, children: [
|
|
3828
3817
|
navigable && /* @__PURE__ */ jsxs30("div", { className: "ui-app__rail", role: "region", "aria-label": "Sidebar", children: [
|
|
3829
3818
|
/* @__PURE__ */ jsx35("div", { className: "ui-app__head", children: lockup }),
|
|
3830
|
-
sidebar(
|
|
3819
|
+
sidebar("Sections"),
|
|
3831
3820
|
/* @__PURE__ */ jsx35("div", { className: "ui-app__foot", children: /* @__PURE__ */ jsx35("div", { className: "ui-app__fold-row", children: /* @__PURE__ */ jsxs30("button", { type: "button", className: "ui-nav__item ui-app__fold", "aria-expanded": !collapsed, "aria-label": foldLabel, onClick: fold, children: [
|
|
3832
3821
|
/* @__PURE__ */ jsx35("span", { className: "ui-nav__ic", children: /* @__PURE__ */ jsxs30("svg", { className: "ui-icon", viewBox: "0 0 24 24", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
3833
3822
|
/* @__PURE__ */ jsx35("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
|
|
@@ -3839,6 +3828,23 @@ function AppShell({
|
|
|
3839
3828
|
] }),
|
|
3840
3829
|
/* @__PURE__ */ jsxs30("div", { className: "ui-app__well", children: [
|
|
3841
3830
|
/* @__PURE__ */ jsxs30("header", { className: "ui-app__bar", children: [
|
|
3831
|
+
navigable && /* @__PURE__ */ jsxs30(Fragment12, { children: [
|
|
3832
|
+
/* @__PURE__ */ jsx35(
|
|
3833
|
+
Button,
|
|
3834
|
+
{
|
|
3835
|
+
ref: menuButton,
|
|
3836
|
+
variant: "ghost",
|
|
3837
|
+
icon: "menu",
|
|
3838
|
+
iconOnly: true,
|
|
3839
|
+
"aria-label": "Menu",
|
|
3840
|
+
className: "ui-app__menu",
|
|
3841
|
+
"aria-haspopup": "dialog",
|
|
3842
|
+
"aria-expanded": menu,
|
|
3843
|
+
onClick: () => setMenu(true)
|
|
3844
|
+
}
|
|
3845
|
+
),
|
|
3846
|
+
/* @__PURE__ */ jsx35("a", { className: "ui-app__brand ui-app__brand--band", href: brandHref, "aria-label": word, children: brand ?? /* @__PURE__ */ jsx35("i", { "aria-hidden": "true", className: "ui-react-app__mark", dangerouslySetInnerHTML: { __html: prism(`${uid}-band`, 24) } }) })
|
|
3847
|
+
] }),
|
|
3842
3848
|
!navigable && lockup,
|
|
3843
3849
|
/* @__PURE__ */ jsxs30("button", { type: "button", className: "ui-app__search", "aria-haspopup": "dialog", onClick: () => setSearch(true), children: [
|
|
3844
3850
|
/* @__PURE__ */ jsx35("span", { className: "ui-app__search-ic", children: /* @__PURE__ */ jsx35(Icon, { name: "search" }) }),
|
|
@@ -3847,7 +3853,7 @@ function AppShell({
|
|
|
3847
3853
|
/* @__PURE__ */ jsx35("kbd", { className: "ui-cmdk__key", children: paletteHotkey() })
|
|
3848
3854
|
] }),
|
|
3849
3855
|
/* @__PURE__ */ jsx35("span", { className: "ui-app__bar-gap" }),
|
|
3850
|
-
bandControl,
|
|
3856
|
+
bandControl && /* @__PURE__ */ jsx35("span", { className: "ui-react-app__band-control", children: bandControl }),
|
|
3851
3857
|
/* @__PURE__ */ jsx35(ThemeToggle, {}),
|
|
3852
3858
|
/* @__PURE__ */ jsx35("div", { className: "ui-app__user ui-app__user--bar", children: /* @__PURE__ */ jsx35(
|
|
3853
3859
|
Dropdown,
|
|
@@ -3876,24 +3882,7 @@ function AppShell({
|
|
|
3876
3882
|
/* @__PURE__ */ jsx35("div", { className: "ui-app__body", children })
|
|
3877
3883
|
] })
|
|
3878
3884
|
] }),
|
|
3879
|
-
navigable && /* @__PURE__ */ jsx35(
|
|
3880
|
-
links(visible),
|
|
3881
|
-
remaining.length > 0 && /* @__PURE__ */ jsx35("li", { children: /* @__PURE__ */ jsxs30(
|
|
3882
|
-
"button",
|
|
3883
|
-
{
|
|
3884
|
-
type: "button",
|
|
3885
|
-
className: `ui-nav__item${active && remaining.includes(active) ? " is-active" : ""}`,
|
|
3886
|
-
"aria-haspopup": "dialog",
|
|
3887
|
-
"aria-expanded": more,
|
|
3888
|
-
onClick: () => setMore(true),
|
|
3889
|
-
children: [
|
|
3890
|
-
/* @__PURE__ */ jsx35("span", { className: "ui-nav__ic", children: /* @__PURE__ */ jsx35(Icon, { name: "moreHorizontal" }) }),
|
|
3891
|
-
/* @__PURE__ */ jsx35("span", { className: "ui-nav__label", children: "More" })
|
|
3892
|
-
]
|
|
3893
|
-
}
|
|
3894
|
-
) })
|
|
3895
|
-
] }) }),
|
|
3896
|
-
/* @__PURE__ */ jsx35(Drawer, { open: more, onClose: () => setMore(false), title: "More sections", side: "bottom", children: sidebar(remaining, "More sections") }),
|
|
3885
|
+
navigable && /* @__PURE__ */ jsx35(Drawer, { open: menu, onClose: () => setMenu(false), title: word, side: "left", size: "sm", children: /* @__PURE__ */ jsx35("div", { className: "ui-react-app__menu", children: sidebar("Menu sections") }) }),
|
|
3897
3886
|
/* @__PURE__ */ jsx35(CommandPalette, { ...palette ?? { groups: [{ items: sections.map((section) => ({ id: section.href, ...section })) }] }, open: search, onClose: () => setSearch(false) })
|
|
3898
3887
|
] });
|
|
3899
3888
|
}
|
|
@@ -3921,7 +3910,7 @@ function EmptyState({ variant = "first-run", title, sub, icon: icon7, art, actio
|
|
|
3921
3910
|
}
|
|
3922
3911
|
|
|
3923
3912
|
// src/Success.tsx
|
|
3924
|
-
import { forwardRef as forwardRef9, useEffect as useEffect15, useRef as
|
|
3913
|
+
import { forwardRef as forwardRef9, useEffect as useEffect15, useRef as useRef20, useState as useState15 } from "react";
|
|
3925
3914
|
import { jsx as jsx37, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
3926
3915
|
var SuccessCheck = forwardRef9(function SuccessCheck2({ variant = "line", className, ...rest }, ref) {
|
|
3927
3916
|
const circled = variant === "circled";
|
|
@@ -3969,7 +3958,7 @@ var pieces = [
|
|
|
3969
3958
|
];
|
|
3970
3959
|
function Countdown({ seconds, label = "Redirecting", onDone }) {
|
|
3971
3960
|
const [remaining, setRemaining] = useState15(seconds);
|
|
3972
|
-
const callback =
|
|
3961
|
+
const callback = useRef20(onDone);
|
|
3973
3962
|
useEffect15(() => {
|
|
3974
3963
|
callback.current = onDone;
|
|
3975
3964
|
}, [onDone]);
|
|
@@ -4050,7 +4039,7 @@ import {
|
|
|
4050
4039
|
useId as useId13,
|
|
4051
4040
|
useLayoutEffect as useLayoutEffect5,
|
|
4052
4041
|
useMemo as useMemo4,
|
|
4053
|
-
useRef as
|
|
4042
|
+
useRef as useRef21,
|
|
4054
4043
|
useState as useState16
|
|
4055
4044
|
} from "react";
|
|
4056
4045
|
import { CHART_FLOOR, bridgeWalk, chartScale, pointTarget } from "@apliteni/apliteni-ui";
|
|
@@ -4216,16 +4205,16 @@ function Chart(props) {
|
|
|
4216
4205
|
const plotHeight = height ?? HEIGHT[variant];
|
|
4217
4206
|
const showTable = table ?? !spark;
|
|
4218
4207
|
const uid = useId13().replace(/[^\w-]/g, "");
|
|
4219
|
-
const frameEl =
|
|
4220
|
-
const scroller =
|
|
4221
|
-
const plot =
|
|
4222
|
-
const tipEl =
|
|
4223
|
-
const svgEl =
|
|
4224
|
-
const ignoreMouseUntil =
|
|
4225
|
-
const touchMoved =
|
|
4226
|
-
const dismissed =
|
|
4227
|
-
const opening =
|
|
4228
|
-
const pointerFocus =
|
|
4208
|
+
const frameEl = useRef21(null);
|
|
4209
|
+
const scroller = useRef21(null);
|
|
4210
|
+
const plot = useRef21(null);
|
|
4211
|
+
const tipEl = useRef21(null);
|
|
4212
|
+
const svgEl = useRef21(null);
|
|
4213
|
+
const ignoreMouseUntil = useRef21(0);
|
|
4214
|
+
const touchMoved = useRef21(false);
|
|
4215
|
+
const dismissed = useRef21(null);
|
|
4216
|
+
const opening = useRef21(false);
|
|
4217
|
+
const pointerFocus = useRef21(false);
|
|
4229
4218
|
const floor = useTapFloor();
|
|
4230
4219
|
const [width, setWidth] = useState16(0);
|
|
4231
4220
|
const [openId, setOpenId] = useState16(null);
|
|
@@ -4765,7 +4754,7 @@ var AccentPicker = forwardRef10(function AccentPicker2({
|
|
|
4765
4754
|
});
|
|
4766
4755
|
|
|
4767
4756
|
// src/NavTabs.tsx
|
|
4768
|
-
import { Fragment as Fragment14, forwardRef as forwardRef11, useEffect as useEffect17, useRef as
|
|
4757
|
+
import { Fragment as Fragment14, forwardRef as forwardRef11, useEffect as useEffect17, useRef as useRef22, useImperativeHandle as useImperativeHandle2 } from "react";
|
|
4769
4758
|
import { safeUrl, observeNavFit, revealCurrentNav, revealFocusedNav } from "@apliteni/apliteni-ui";
|
|
4770
4759
|
import { Fragment as Fragment15, jsx as jsx40, jsxs as jsxs34 } from "react/jsx-runtime";
|
|
4771
4760
|
var NavTabs = forwardRef11(function NavTabs2({
|
|
@@ -4778,7 +4767,7 @@ var NavTabs = forwardRef11(function NavTabs2({
|
|
|
4778
4767
|
"aria-label": ariaLabel = "Tabs",
|
|
4779
4768
|
...rest
|
|
4780
4769
|
}, ref) {
|
|
4781
|
-
const navRef =
|
|
4770
|
+
const navRef = useRef22(null);
|
|
4782
4771
|
useImperativeHandle2(ref, () => navRef.current, []);
|
|
4783
4772
|
const signature = items.map((item) => item.id).join("\0");
|
|
4784
4773
|
useEffect17(() => observeNavFit(navRef.current), []);
|
package/src/assets/icons.js
CHANGED
|
@@ -180,6 +180,10 @@ export const iconOnlyAllowed = {
|
|
|
180
180
|
// is the glyph a reader has already met doing this everywhere else. The entry
|
|
181
181
|
// is the decision — review did not infer it from the glyph looking obvious.
|
|
182
182
|
download: 'export or download the rows',
|
|
183
|
+
// The phone shell's menu control, decided in #592: below 720px the icon strip goes away
|
|
184
|
+
// and one button opens the full navigation in a drawer. The three-bar glyph is the
|
|
185
|
+
// reader's own mark for this action.
|
|
186
|
+
menu: 'open the navigation drawer',
|
|
183
187
|
};
|
|
184
188
|
|
|
185
189
|
// The word a wordless control's name may open with. The list above says which
|
|
@@ -200,6 +204,7 @@ export const iconOnlyNames = {
|
|
|
200
204
|
// depicts it, so a consumer naming it that way is not borrowing the tray for
|
|
201
205
|
// something else.
|
|
202
206
|
download: ['export', 'download'],
|
|
207
|
+
menu: ['menu'],
|
|
203
208
|
};
|
|
204
209
|
|
|
205
210
|
// What a glyph means when a component picks it for the reader rather than a
|
package/src/components/drawer.js
CHANGED
|
@@ -153,10 +153,13 @@ export function wireDrawer(root = document) {
|
|
|
153
153
|
doc.__drawerGlobalWired = true;
|
|
154
154
|
// Any [data-drawer-open="ID"] trigger opens the matching drawer.
|
|
155
155
|
doc.addEventListener('click', (e) => {
|
|
156
|
-
|
|
156
|
+
// composedPath() and not target: a trigger inside an open shadow root is retargeted
|
|
157
|
+
// to its host here, and its drawer is found in the tree the trigger is in.
|
|
158
|
+
const opener = e.composedPath().find((n) => n.nodeType === 1 && n.hasAttribute('data-drawer-open'));
|
|
157
159
|
if (!opener) return;
|
|
158
160
|
e.preventDefault();
|
|
159
|
-
const
|
|
161
|
+
const id = opener.getAttribute('data-drawer-open');
|
|
162
|
+
const target = (opener.getRootNode().getElementById?.(id)) || doc.getElementById(id);
|
|
160
163
|
if (target) openDrawer(target, opener);
|
|
161
164
|
});
|
|
162
165
|
}
|
|
@@ -51,20 +51,24 @@ export function focusablesIn(panel) {
|
|
|
51
51
|
return Array.from(panel.querySelectorAll(FOCUSABLE)).filter(reachable);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
// Hide everything *outside* `root` from AT + the tab order: walk
|
|
54
|
+
// Hide everything *outside* `root` from AT + the tab order: walk through a shadow
|
|
55
|
+
// host when present, then to body, and mark each
|
|
55
56
|
// ancestor's other children. Scrim and panel live inside the root so they stay
|
|
56
57
|
// interactive; a drawer under an aria-modal alertdialog is outside it and must not be.
|
|
57
58
|
function mark(page, doc, root) {
|
|
58
59
|
let node = root;
|
|
59
|
-
while (node && node
|
|
60
|
-
|
|
60
|
+
while (node && node !== doc.body) {
|
|
61
|
+
const tree = node.getRootNode();
|
|
62
|
+
const parent = node.parentElement || (tree.host ? tree : null);
|
|
63
|
+
if (!parent) break;
|
|
64
|
+
for (const sib of parent.children) {
|
|
61
65
|
if (sib === node) continue;
|
|
62
66
|
page.marked.push([sib, sib.getAttribute('aria-hidden'), sib.hasAttribute('inert')]);
|
|
63
67
|
sib.setAttribute('aria-hidden', 'true');
|
|
64
68
|
sib.setAttribute('inert', '');
|
|
65
69
|
sib.inert = true;
|
|
66
70
|
}
|
|
67
|
-
node =
|
|
71
|
+
node = parent.host || parent;
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
|
|
@@ -248,7 +252,7 @@ export function trapTab(panel, e) {
|
|
|
248
252
|
if (!items.length) { e.preventDefault(); panel.focus(); return; }
|
|
249
253
|
const first = items[0];
|
|
250
254
|
const last = items[items.length - 1];
|
|
251
|
-
const active =
|
|
255
|
+
const active = panel.getRootNode().activeElement || panel.ownerDocument.activeElement;
|
|
252
256
|
if (!panel.contains(active)) {
|
|
253
257
|
e.preventDefault(); (e.shiftKey ? last : first).focus();
|
|
254
258
|
} else if (e.shiftKey && (active === first || active === panel)) {
|
package/src/components/shell.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
// wireShell() once after mounting wires the fold, the nav's groups and the reader's menu.
|
|
4
4
|
// why: docs/components.md#the-page-shell
|
|
5
5
|
import { topbar as productTopbar } from './topbar.js';
|
|
6
|
-
import { esc, icon } from './index.js';
|
|
6
|
+
import { esc, icon, button } from './index.js';
|
|
7
7
|
import { sidebarNav, breadcrumbs, wireNav } from './nav.js';
|
|
8
8
|
import { dropdown, wireDropdown } from './dropdown.js';
|
|
9
|
+
import { drawer, wireDrawer, closeDrawer } from './drawer.js';
|
|
9
10
|
import { backLink } from './back.js';
|
|
10
11
|
import { prism } from '../assets/brand.js';
|
|
11
12
|
import { ACCOUNT_NAV, toMenuTuple, initials } from './account-nav.js';
|
|
@@ -207,7 +208,7 @@ const readerFace = (name, email, named, markOnly = false) =>
|
|
|
207
208
|
//
|
|
208
209
|
// One block, two places: only the direction it opens is the layout's answer.
|
|
209
210
|
// `portal: true` either way — the rail and the band are both sticky.
|
|
210
|
-
function readerBlock({ name, email }, signOutHref, { band = false } = {}) {
|
|
211
|
+
function readerBlock({ name, email }, signOutHref, { band = false, portal = true } = {}) {
|
|
211
212
|
if (!name && !email) return '';
|
|
212
213
|
const cls = `ui-app__user${band ? ' ui-app__user--bar' : ''}`;
|
|
213
214
|
const who = [name, email].filter(Boolean).join(', ');
|
|
@@ -225,7 +226,7 @@ function readerBlock({ name, email }, signOutHref, { band = false } = {}) {
|
|
|
225
226
|
+ `</div>`;
|
|
226
227
|
return `<div class="${cls}">${dropdown({
|
|
227
228
|
variant: 'menu',
|
|
228
|
-
portal
|
|
229
|
+
portal,
|
|
229
230
|
direction: band ? 'down' : 'up',
|
|
230
231
|
align: band ? 'end' : 'start',
|
|
231
232
|
chevron: !band,
|
|
@@ -253,13 +254,26 @@ const searchField = ({ palette, placeholder, shortLabel }) =>
|
|
|
253
254
|
|
|
254
255
|
// The band: search at its start, the reader at its end, beside the rail rather than
|
|
255
256
|
// across the top of both. why: docs/components.md#the-page-shell
|
|
256
|
-
const shellBar = (search, account, signOutHref) =>
|
|
257
|
+
const shellBar = (search, account, signOutHref, lead = '') =>
|
|
257
258
|
`<header class="ui-app__bar">`
|
|
259
|
+
+ lead
|
|
258
260
|
+ (search ? searchField(search) : '')
|
|
259
261
|
+ `<span class="ui-app__bar-gap"></span>`
|
|
260
262
|
+ readerBlock(account, signOutHref, { band: true })
|
|
261
263
|
+ `</header>`;
|
|
262
264
|
|
|
265
|
+
// Below 720px the rail is not drawn. A band holds the menu control and the brand's mark,
|
|
266
|
+
// and the control opens the same rail in the kit's left drawer. Each part carries
|
|
267
|
+
// `ui-app__phone`, so layout.css draws it at that width only.
|
|
268
|
+
// why: docs/components.md#the-page-shell
|
|
269
|
+
const menuControl = (id) => button({ label: 'Menu', variant: 'ghost', icon: 'menu', iconOnly: true })
|
|
270
|
+
.replace('<button ', `<button data-drawer-open="${id}" aria-haspopup="dialog" `)
|
|
271
|
+
.replace('class="', 'class="ui-app__menu ');
|
|
272
|
+
|
|
273
|
+
const bandBrand = (word, href, uid) =>
|
|
274
|
+
`<a class="ui-app__brand ui-app__brand--band" href="${esc(safeUrl(href))}" aria-label="${esc(word)}">`
|
|
275
|
+
+ `${prism(`appb-${uid}`, 24)}</a>`;
|
|
276
|
+
|
|
263
277
|
// Unique-per-render suffix for the brand mark's clip id — the same reason
|
|
264
278
|
// nav.js keeps a module counter. Two shells on one page must not collide.
|
|
265
279
|
let _shellUid = 0;
|
|
@@ -291,6 +305,7 @@ export function appShell(options = {}) {
|
|
|
291
305
|
// The layout's one branch, read once. Everything below asks this and not the string.
|
|
292
306
|
const banded = layout === 'topbar';
|
|
293
307
|
const up = back ? backLink(back) : '';
|
|
308
|
+
const menuId = `ui-app-menu-${++_shellUid}`;
|
|
294
309
|
// No footer slot: the nav is places to go, and the one thing that was in it —
|
|
295
310
|
// sign out — is in the reader's menu at the rail's foot.
|
|
296
311
|
const rail = sidebarNav({
|
|
@@ -331,6 +346,27 @@ export function appShell(options = {}) {
|
|
|
331
346
|
const foot = banded
|
|
332
347
|
? (fold ? `<div class="ui-app__foot">${fold}</div>` : '')
|
|
333
348
|
: readerBlock(account, signOutHref);
|
|
349
|
+
// The phone's way to the same rail: the menu control, and the drawer it opens. The
|
|
350
|
+
// drawer holds a second copy of the nav and the reader, because a drawer is a dialog
|
|
351
|
+
// and the rail is a landmark; layout.css draws one or the other. The banded layout
|
|
352
|
+
// keeps its reader on the band, so its drawer has no foot.
|
|
353
|
+
const menu = menuControl(menuId);
|
|
354
|
+
const mark = bandBrand(word, brandHref, ++_shellUid);
|
|
355
|
+
const phone = `<div class="ui-app__drawer">${drawer({
|
|
356
|
+
side: 'left',
|
|
357
|
+
size: 'sm',
|
|
358
|
+
title: word,
|
|
359
|
+
id: menuId,
|
|
360
|
+
body: sidebarNav({
|
|
361
|
+
sections: [{ label: navLabel, items: nav }],
|
|
362
|
+
active,
|
|
363
|
+
activeIs: up ? 'section' : 'page',
|
|
364
|
+
ariaLabel: navLabel,
|
|
365
|
+
}),
|
|
366
|
+
// Not portalled: a panel lifted to the page would sit outside the open drawer, inert
|
|
367
|
+
// and under it, so Sign out could not be reached.
|
|
368
|
+
footer: banded ? '' : readerBlock(account, signOutHref, { portal: false }),
|
|
369
|
+
})}</div>`;
|
|
334
370
|
const main = `<main class="ui-app__main${width === 'wide' ? ' ui-app__main--wide' : ''}"${maxWidth ? ` style="--ui-app-main: ${maxWidth}"` : ''}>
|
|
335
371
|
${up || (crumbs.length ? breadcrumbs({ items: crumbs }) : '')}
|
|
336
372
|
${title ? `<h1>${title}</h1>` : ''}
|
|
@@ -339,8 +375,8 @@ export function appShell(options = {}) {
|
|
|
339
375
|
</main>`;
|
|
340
376
|
// The second column is a box of its own: the band and the page are two rows of it.
|
|
341
377
|
const well = banded
|
|
342
|
-
? `<div class="ui-app__well">${shellBar(search, account, signOutHref)}${main}</div>`
|
|
343
|
-
: main
|
|
378
|
+
? `<div class="ui-app__well">${shellBar(search, account, signOutHref, menu + mark)}${main}</div>`
|
|
379
|
+
: `${compat ? '' : `<header class="ui-app__bar ui-app__bar--phone">${menu}${mark}</header>`}${main}`;
|
|
344
380
|
const grid = `<div class="ui-app${banded ? ' ui-app--topbar' : ''}${folded ? ' is-collapsed' : ''}"${auto}>
|
|
345
381
|
<div class="ui-app__rail" role="region" aria-label="Sidebar">
|
|
346
382
|
${head}
|
|
@@ -348,8 +384,11 @@ export function appShell(options = {}) {
|
|
|
348
384
|
${foot}
|
|
349
385
|
</div>
|
|
350
386
|
${well}
|
|
387
|
+
${phone}
|
|
351
388
|
</div>`;
|
|
352
|
-
return compat
|
|
389
|
+
return compat
|
|
390
|
+
? `<div class="ui-app-page">${productTopbar(compat).replace('<div class="topbar__in">', `<div class="topbar__in">${menu}`)}${grid}</div>`
|
|
391
|
+
: grid;
|
|
353
392
|
}
|
|
354
393
|
|
|
355
394
|
// ---- Behaviour -----------------------------------------------------------
|
|
@@ -358,6 +397,9 @@ export function appShell(options = {}) {
|
|
|
358
397
|
// keeps every shell under root out of the cookie, shells drawn there later
|
|
359
398
|
// included; only `persist: true` on that root turns it back on.
|
|
360
399
|
const _wiredDocs = new WeakSet();
|
|
400
|
+
// The roots wireShell() was handed, per document: a resize has to find a drawer open
|
|
401
|
+
// inside a shadow root, which the document's own querySelectorAll does not enter.
|
|
402
|
+
const _wiredRoots = new WeakMap();
|
|
361
403
|
const _unpersisted = new WeakSet();
|
|
362
404
|
|
|
363
405
|
// The toggle, addressed from the rail that owns it: a fold row of a shell's own rail
|
|
@@ -369,6 +411,8 @@ const _unpersisted = new WeakSet();
|
|
|
369
411
|
const FOLD_PATH = '.ui-app__fold-row > [data-rail-toggle]';
|
|
370
412
|
const RAIL_FOLD = `.ui-app__rail ${FOLD_PATH}`;
|
|
371
413
|
|
|
414
|
+
const DRAWER_LINK = '.ui-app__drawer a.ui-nav__item[href]';
|
|
415
|
+
|
|
372
416
|
// Under an opted-out root? Steps out of a shadow root through its host.
|
|
373
417
|
const optedOut = (node) => {
|
|
374
418
|
for (let n = node; n; n = n.parentNode || n.host) if (_unpersisted.has(n)) return true;
|
|
@@ -390,13 +434,31 @@ function setRail(app, collapsed) {
|
|
|
390
434
|
function listen(doc) {
|
|
391
435
|
if (_wiredDocs.has(doc)) return;
|
|
392
436
|
_wiredDocs.add(doc);
|
|
437
|
+
// A drawer left open while the window grows past the phone width would be hidden and
|
|
438
|
+
// still hold the page inert, so it closes there. Its wrapper is not drawn above that
|
|
439
|
+
// width, which is how the stylesheet's breakpoint is read without a second copy of it.
|
|
440
|
+
const view = doc.defaultView;
|
|
441
|
+
if (view) {
|
|
442
|
+
view.addEventListener('resize', () => {
|
|
443
|
+
for (const r of _wiredRoots.get(doc) || []) {
|
|
444
|
+
r.querySelectorAll('.ui-app__drawer .ui-drawer.is-open').forEach((d) => {
|
|
445
|
+
if (view.getComputedStyle(d.parentElement).display === 'none') closeDrawer(d);
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
393
450
|
doc.addEventListener('click', (e) => {
|
|
394
451
|
// A shell's own toggle only, in the head of its own rail: a stray
|
|
395
452
|
// [data-rail-toggle] in the page body folds nothing. composedPath() rather
|
|
396
453
|
// than target, so a shell inside an open shadow root is found. closest()
|
|
397
454
|
// rather than a chain of parents, which the head band made one link longer
|
|
398
455
|
// and which said nothing about what it was walking through.
|
|
399
|
-
const
|
|
456
|
+
const path = e.composedPath();
|
|
457
|
+
// A press on a place in the phone drawer closes it. A group's toggle is a button
|
|
458
|
+
// and goes nowhere, so only a link does. why: docs/components.md#the-page-shell
|
|
459
|
+
const row = path.find((n) => n.nodeType === 1 && n.matches(DRAWER_LINK));
|
|
460
|
+
if (row) closeDrawer(row.closest('.ui-drawer'));
|
|
461
|
+
const btn = path.find((n) => n.nodeType === 1 && n.matches(RAIL_FOLD));
|
|
400
462
|
const app = btn && btn.closest('.ui-app');
|
|
401
463
|
if (!app) return;
|
|
402
464
|
const next = !app.classList.contains('is-collapsed');
|
|
@@ -408,7 +470,6 @@ function listen(doc) {
|
|
|
408
470
|
} catch (err) { /* a sandboxed frame: the fold works, nothing is kept */ }
|
|
409
471
|
}
|
|
410
472
|
// A document with no window (DOMParser, createHTMLDocument) has no CustomEvent to send.
|
|
411
|
-
const view = doc.defaultView;
|
|
412
473
|
if (view) {
|
|
413
474
|
app.dispatchEvent(new view.CustomEvent('ui-rail', { bubbles: true, composed: true, detail: { collapsed: next } }));
|
|
414
475
|
}
|
|
@@ -420,6 +481,7 @@ export function wireShell(root = document, { persist } = {}) {
|
|
|
420
481
|
// The reader's menu is a dropdown() like any other, so it is wired like any
|
|
421
482
|
// other. Idempotent, and a shell with no account draws none to find.
|
|
422
483
|
wireDropdown(root);
|
|
484
|
+
wireDrawer(root);
|
|
423
485
|
// A server cannot know which key the reader holds, so the browser corrects it here —
|
|
424
486
|
// off the ROOT's window, not the global one. why: docs/components.md#the-page-shell
|
|
425
487
|
const here = root.nodeType === 9 ? root : root.ownerDocument;
|
|
@@ -428,6 +490,10 @@ export function wireShell(root = document, { persist } = {}) {
|
|
|
428
490
|
for (const kbd of root.querySelectorAll('[data-palette-hotkey]')) kbd.textContent = key;
|
|
429
491
|
const doc = root.nodeType === 9 ? root : root.ownerDocument;
|
|
430
492
|
listen(doc);
|
|
493
|
+
if (!_wiredRoots.has(doc)) _wiredRoots.set(doc, new Set());
|
|
494
|
+
const roots = _wiredRoots.get(doc);
|
|
495
|
+
for (const r of roots) if (r !== doc && !(r.host || r).isConnected) roots.delete(r);
|
|
496
|
+
roots.add(root);
|
|
431
497
|
if (persist === false) _unpersisted.add(root);
|
|
432
498
|
else if (persist === true) _unpersisted.delete(root);
|
|
433
499
|
const saved = railCollapsed(cookieOf(doc));
|
package/src/styles/layout.css
CHANGED
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
resting step. */
|
|
120
120
|
/* Re-tuned at #295, which took the light rail down a step with the page. Below 720px
|
|
121
121
|
the glyph is the whole row, so it is held at text grade rather than at 1.4.11's bar —
|
|
122
|
-
stories/apps/shell-states.test.js:
|
|
122
|
+
stories/apps/shell-states.test.js:1535 `a resting glyph is dimmer but still legible`. */
|
|
123
123
|
.ui-app__rail .ui-nav__ic svg { opacity: 0.66; }
|
|
124
124
|
|
|
125
125
|
/* A destructive row of the rail is a navigation row and rests like one; the danger
|
|
@@ -139,6 +139,8 @@
|
|
|
139
139
|
padding-top: var(--space-5);
|
|
140
140
|
border-top: 1px solid var(--border);
|
|
141
141
|
}
|
|
142
|
+
/* A drawer's footer carries no rule above it, so the block keeps the rail's rule out. */
|
|
143
|
+
.ui-app__drawer .ui-app__user { padding-top: 0; border-top: 0; }
|
|
142
144
|
/* The other thing that can close a rail: where the reader's block is in the band,
|
|
143
145
|
the fold's control takes its place under the same rule.
|
|
144
146
|
why: docs/components.md#the-page-shell */
|
|
@@ -254,8 +256,7 @@
|
|
|
254
256
|
.ui-app.is-collapsed .ui-app__fold .ui-app__fold-arrow { transform: scaleX(-1); }
|
|
255
257
|
|
|
256
258
|
/* -- The collapsed rail --------------------------------------------------- */
|
|
257
|
-
/*
|
|
258
|
-
again. why: docs/components.md#the-page-shell */
|
|
259
|
+
/* The reader's own fold, at the widths that have a rail. why: docs/components.md#the-page-shell */
|
|
259
260
|
.ui-app.is-collapsed { --ui-rail-w: calc(var(--ui-nav-strip) + 2 * var(--space-4) + var(--ui-rail-rule)); }
|
|
260
261
|
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__cap,
|
|
261
262
|
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__label,
|
|
@@ -263,20 +264,17 @@
|
|
|
263
264
|
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__caret,
|
|
264
265
|
:where(.ui-app.is-collapsed) .ui-app__rail .ui-app__who,
|
|
265
266
|
:where(.ui-app.is-collapsed) .ui-app__rail .ui-dropdown__chevron,
|
|
266
|
-
:where(.ui-app.is-collapsed) .ui-app__brand span { opacity: 0; pointer-events: none; }
|
|
267
|
+
:where(.ui-app.is-collapsed) .ui-app__rail .ui-app__brand span { opacity: 0; pointer-events: none; }
|
|
267
268
|
/* The toggle rides onto the glyph column — the two widths' own difference — and the
|
|
268
|
-
lockup goes whole, since that column is where its mark stood.
|
|
269
|
-
|
|
270
|
-
gives the lockup back rather than leaving an empty band with a hairline under it.
|
|
271
|
-
why: docs/components.md#the-page-shell */
|
|
269
|
+
lockup goes whole, since that column is where its mark stood. Below 720px there is
|
|
270
|
+
no rail, so none of this is drawn. why: docs/components.md#the-page-shell */
|
|
272
271
|
:where(.ui-app.is-collapsed) .ui-app__head > .ui-app__fold-row { inset-inline-start: calc(var(--ui-nav-strip) - var(--ui-nav-col)); }
|
|
273
272
|
/* …and only in the layout where something arrives on the mark's column. In the
|
|
274
273
|
topbar layout the toggle is at the rail's foot, nothing lands where the mark
|
|
275
274
|
stands, and a lockup faded out there leaves a band with nothing in it and its
|
|
276
|
-
hairline still under it
|
|
277
|
-
that layout keeps the mark and lets the shared rule above fold away its words.
|
|
275
|
+
hairline still under it. So that layout keeps the mark and lets the shared rule above fold away its words.
|
|
278
276
|
why: docs/components.md#the-page-shell */
|
|
279
|
-
:where(.ui-app:not(.ui-app--topbar).is-collapsed) .ui-app__brand { opacity: 0; visibility: hidden; pointer-events: none; }
|
|
277
|
+
:where(.ui-app:not(.ui-app--topbar).is-collapsed) .ui-app__rail .ui-app__brand { opacity: 0; visibility: hidden; pointer-events: none; }
|
|
280
278
|
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__sub { margin-inline-start: 0; padding-inline-start: 0; border-inline-start-width: 0; }
|
|
281
279
|
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item:not(:has(.ui-nav__ic))::after { opacity: 0.62; }
|
|
282
280
|
/* The current row's plate on the glyph column, and beside it the one thing a bare rail
|
|
@@ -502,61 +500,48 @@
|
|
|
502
500
|
.ui-toolbar--split > :last-child { margin-inline-start: 0; align-self: start; }
|
|
503
501
|
}
|
|
504
502
|
|
|
505
|
-
/* -- The
|
|
506
|
-
/* Below this width
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
503
|
+
/* -- The phone shell ------------------------------------------------------ */
|
|
504
|
+
/* Below this width there is no rail and no strip. A band at the top holds the menu
|
|
505
|
+
control and the brand's mark, the control opens the same rail in the kit's left
|
|
506
|
+
drawer, and the reading column takes the whole width. The band, the control and the
|
|
507
|
+
drawer exist at every width and are not drawn above it, so the rail's rows above
|
|
508
|
+
720px are the rows they were. why: docs/components.md#the-page-shell */
|
|
509
|
+
.ui-app__bar--phone { display: none; }
|
|
510
|
+
.ui-btn.ui-app__menu { display: none; }
|
|
511
|
+
.ui-app__brand--band { display: none; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; /* a finger's box around a 24px mark */ }
|
|
512
|
+
.ui-app__drawer { display: none; }
|
|
513
|
+
/* The drawer panel is --bg-elevated, which --surface matches exactly in light, so nav.css's
|
|
514
|
+
plate measures 1.00:1 there. The current row and the hover wash take the rung the rail
|
|
515
|
+
takes (--surface-3), and the count keeps its --surface pill on that plate. Dark already
|
|
516
|
+
has a step and keeps it. why: docs/components.md#the-page-shell */
|
|
517
|
+
:where(:root[data-theme="light"]) .ui-app__drawer .ui-nav__item:not(.is-danger):not(.is-active):hover { background: color-mix(in srgb, var(--surface-3) 50%, transparent); /* code-bg: inherit — The wash keeps the containing ground. */ }
|
|
518
|
+
:where(:root[data-theme="light"]) .ui-app__drawer .ui-nav__item.is-active { background: var(--surface-3); /* code-bg: inherit — The control keeps the containing ground. */ }
|
|
511
519
|
@media (max-width: 720px) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
.ui-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
.ui-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
.ui-
|
|
525
|
-
/*
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
.ui-
|
|
532
|
-
.ui-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
.ui-
|
|
536
|
-
.ui-
|
|
537
|
-
.ui-app__rail .ui-dropdown__chevron,
|
|
538
|
-
.ui-app__brand span { opacity: 0; pointer-events: none; }
|
|
539
|
-
.ui-app__rail .ui-nav__sub { margin-inline-start: 0; padding-inline-start: 0; border-inline-start-width: 0; }
|
|
540
|
-
.ui-app__rail .ui-nav__item:not(:has(.ui-nav__ic))::after { opacity: 0.62; }
|
|
541
|
-
.ui-app__rail .ui-nav__item.is-active { isolation: isolate; background: none; }
|
|
542
|
-
.ui-app__rail .ui-nav__item.is-active::before { content: ""; position: absolute; left: 0; top: 50%; width: var(--ui-nav-strip); height: 100%; border-radius: 10px; transform: translateY(-50%); /* motion: still — a mark on the current row, whose own highlight already transitions; this centres it, it does not travel */ background: var(--surface-3); /* code-bg: inherit — The plate contains no focus target. */ z-index: -1; }
|
|
543
|
-
.ui-app__rail .ui-nav__item:not(.is-danger):not(.is-active):hover { background: none; }
|
|
544
|
-
/* The one line of this block that is deliberately NOT the reader's fold: a finger
|
|
545
|
-
is the only pointer this width has, so a row and the account block are held to
|
|
546
|
-
44px. Neither floor can spread to the press, where growing the box is the one
|
|
547
|
-
thing the travel promises nothing does. why: docs/components.md#the-page-shell */
|
|
548
|
-
.ui-app__rail .ui-nav__item,
|
|
549
|
-
.ui-app__rail .ui-app__user-trigger { min-height: 44px; }
|
|
550
|
-
.ui-app__rail .ui-nav__item:is(:hover, :focus-visible) > .ui-nav__label,
|
|
551
|
-
.ui-app__rail .ui-app__user-trigger:is(:hover, :focus-visible) > .ui-app__who { position: fixed; left: calc(var(--ui-rail-w) + var(--space-2)); z-index: var(--z-dropdown); width: max-content; max-width: 15rem; margin-block: -7px; padding: 6px 10px; border: 1px solid var(--float-edge); box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop); border-radius: var(--radius-xs); background: var(--surface); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */ color: var(--strong); opacity: 1; pointer-events: none; }
|
|
552
|
-
@supports (anchor-name: --a) and (anchor-scope: --a) {
|
|
553
|
-
.ui-app { anchor-scope: --ui-rail; }
|
|
554
|
-
.ui-app__rail { anchor-name: --ui-rail; }
|
|
555
|
-
.ui-app__rail .ui-nav__item,
|
|
556
|
-
.ui-app__rail .ui-app__user-trigger { anchor-name: --ui-rail-row; anchor-scope: --ui-rail-row; }
|
|
557
|
-
.ui-app__rail .ui-nav__item:is(:hover, :focus-visible) > .ui-nav__label,
|
|
558
|
-
.ui-app__rail .ui-app__user-trigger:is(:hover, :focus-visible) > .ui-app__who { position-anchor: --ui-rail-row; left: calc(anchor(--ui-rail right) + var(--space-2)); top: anchor(top); bottom: anchor(bottom); align-self: center; margin-block: 0; }
|
|
559
|
-
}
|
|
520
|
+
/* A column, not a grid: the band sticks to the top of the shell, and a sticky grid
|
|
521
|
+
item sticks only inside its own row. */
|
|
522
|
+
.ui-app { display: flex; flex-direction: column; }
|
|
523
|
+
.ui-app > .ui-app__rail { display: none; }
|
|
524
|
+
.ui-app__well { flex: 1 1 auto; }
|
|
525
|
+
.ui-app > .ui-app__main { flex: 1 1 auto; }
|
|
526
|
+
.ui-app > .ui-app__bar--phone { display: flex; }
|
|
527
|
+
.ui-btn.ui-app__menu { display: inline-flex; }
|
|
528
|
+
.ui-app__brand--band { display: flex; }
|
|
529
|
+
/* The menu control stands at the band's start with the band's inset beside it. */
|
|
530
|
+
.ui-app__bar { padding-inline: var(--space-2) var(--space-4); }
|
|
531
|
+
/* The drawer paints itself and sits out of the flow, so its wrapper takes no height. */
|
|
532
|
+
.ui-app__drawer { display: block; }
|
|
533
|
+
/* The rail's rows are the drawer's rows: the column fills the panel, and the foot
|
|
534
|
+
starts at the panel's edge like the rows above it. */
|
|
535
|
+
.ui-app__drawer .ui-nav--side,
|
|
536
|
+
.ui-app__drawer .ui-nav--side > * { width: 100%; }
|
|
537
|
+
/* The panel's title names the list, so the section's own caption stays out. */
|
|
538
|
+
.ui-app__drawer .ui-nav__cap { display: none; }
|
|
539
|
+
.ui-app__drawer .ui-drawer__footer { justify-content: flex-start; }
|
|
540
|
+
.ui-app__drawer .ui-app__user { width: 100%; }
|
|
541
|
+
/* A finger is the only pointer this width has, so a row and the account block are
|
|
542
|
+
held to 44px. why: docs/components.md#the-page-shell */
|
|
543
|
+
.ui-app__drawer .ui-nav__item,
|
|
544
|
+
.ui-app__drawer .ui-app__user-trigger { min-height: 44px; }
|
|
560
545
|
.ui-app__main { padding: 28px var(--space-4) 56px; }
|
|
561
546
|
.ui-app__main h1 { font-size: calc(var(--text-xl) + 3px); }
|
|
562
547
|
/* A settings row is label-beside-control down to the width where the control
|
|
@@ -733,16 +718,3 @@
|
|
|
733
718
|
forced-color-adjust: auto; background: Canvas; color: CanvasText; border-color: CanvasText;
|
|
734
719
|
}
|
|
735
720
|
}
|
|
736
|
-
@media (forced-colors: active) and (max-width: 720px) {
|
|
737
|
-
.ui-app .ui-app__rail .ui-nav__item:is(.is-active, .is-current) { isolation: isolate; background: none !important; }
|
|
738
|
-
.ui-app .ui-app__rail .ui-nav__item.is-current:not(.is-active)::before {
|
|
739
|
-
content: ""; position: absolute; left: 0; top: 50%; width: var(--ui-nav-strip); height: 100%; border-radius: 10px;
|
|
740
|
-
transform: translateY(-50%);
|
|
741
|
-
z-index: -1;
|
|
742
|
-
}
|
|
743
|
-
.ui-app .ui-app__rail .ui-nav__item.is-current:not(.is-active):not(.is-disabled)::before { background: Highlight; }
|
|
744
|
-
.ui-app .ui-app__rail .ui-nav__item.is-current.is-disabled:not(.is-active)::before { background: GrayText; }
|
|
745
|
-
.ui-app .ui-app__rail .ui-nav__item:is(.is-active, .is-current):is(:hover, :focus-visible) > .ui-nav__label {
|
|
746
|
-
forced-color-adjust: auto; background: Canvas; color: CanvasText; border-color: CanvasText;
|
|
747
|
-
}
|
|
748
|
-
}
|
package/src/styles/topbar.css
CHANGED
|
@@ -252,6 +252,15 @@
|
|
|
252
252
|
padding: var(--space-1) var(--space-3) 3px;
|
|
253
253
|
}
|
|
254
254
|
.topbar__in > .brand { grid-area: 1 / 1 / 2 / 3; justify-self: start; }
|
|
255
|
+
/* With the shell's menu control on the band it takes the first column and the brand
|
|
256
|
+
the next, so the switch row below spans both. */
|
|
257
|
+
.topbar__in:has(> .ui-app__menu) { grid-template-columns: max-content max-content minmax(0, 1fr) auto auto; }
|
|
258
|
+
.topbar__in > .ui-app__menu { grid-area: 1 / 1; margin-inline-end: var(--space-2); }
|
|
259
|
+
.topbar__in:has(> .ui-app__menu) > .brand { grid-area: 1 / 2 / 2 / 4; }
|
|
260
|
+
.topbar__in:has(> .ui-app__menu) > .toggle { grid-area: 1 / 4; }
|
|
261
|
+
.topbar__in:has(> .ui-app__menu) > .acct { grid-area: 1 / 5; }
|
|
262
|
+
.topbar__in:has(> .ui-app__menu) > .dtsw { grid-area: 2 / 1 / 3 / 3; }
|
|
263
|
+
.topbar__in:has(> .ui-app__menu) > .vsw { grid-area: 2 / 3 / 3 / -1; }
|
|
255
264
|
.topbar__in > .toggle { grid-area: 1 / 3; justify-self: end; }
|
|
256
265
|
/* The 8px beside the account is its own margin, so a hidden account leaves no gap and
|
|
257
266
|
the theme switch ends on the inset. */
|