@cueplusplus/ui 0.1.0 → 0.2.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.
Files changed (83) hide show
  1. package/CHANGELOG.md +684 -0
  2. package/dist/brand/_chassis.d.ts +54 -0
  3. package/dist/brand/_chassis.js +81 -0
  4. package/dist/brand/_geometry.js +41 -0
  5. package/dist/brand/cue-logotype.d.ts +29 -0
  6. package/dist/brand/cue-logotype.js +60 -0
  7. package/dist/brand/cue-mark.d.ts +30 -0
  8. package/dist/brand/cue-mark.js +61 -0
  9. package/dist/brand/index.d.ts +5 -0
  10. package/dist/brand/plussie.d.ts +67 -0
  11. package/dist/brand/plussie.js +125 -0
  12. package/dist/chat/agent-pile.js +1 -1
  13. package/dist/chat/ask-box.js +2 -2
  14. package/dist/chat/delegation-card.js +1 -1
  15. package/dist/chat/message.js +2 -2
  16. package/dist/chrome/_edge-scroller.d.ts +45 -0
  17. package/dist/chrome/_edge-scroller.js +138 -0
  18. package/dist/chrome/_glyphs.js +19 -1
  19. package/dist/chrome/_tabs-scroll.d.ts +12 -7
  20. package/dist/chrome/_tabs-scroll.js +12 -7
  21. package/dist/chrome/app-bar.d.ts +103 -0
  22. package/dist/chrome/app-bar.js +175 -0
  23. package/dist/chrome/app-shell.d.ts +125 -0
  24. package/dist/chrome/app-shell.js +167 -0
  25. package/dist/chrome/footer.d.ts +75 -0
  26. package/dist/chrome/footer.js +68 -0
  27. package/dist/chrome/index.d.ts +5 -1
  28. package/dist/chrome/index.js +4 -1
  29. package/dist/chrome/navigation-menu.js +1 -1
  30. package/dist/chrome/page-shell.d.ts +1 -1
  31. package/dist/chrome/page-shell.js +15 -5
  32. package/dist/chrome/segmented-control.d.ts +19 -0
  33. package/dist/chrome/segmented-control.js +40 -21
  34. package/dist/chrome/tabs.js +21 -65
  35. package/dist/chrome/toolbar.d.ts +22 -0
  36. package/dist/chrome/toolbar.js +26 -5
  37. package/dist/configurator/_overrides.js +4 -2
  38. package/dist/configurator/configurator.js +1 -1
  39. package/dist/configurator/panel-sections.js +10 -4
  40. package/dist/dmx/channel-matrix.js +1 -1
  41. package/dist/forms/otp-field.js +1 -1
  42. package/dist/index.d.ts +15 -6
  43. package/dist/index.js +15 -9
  44. package/dist/instruments/data-table.js +1 -1
  45. package/dist/layout/carousel.js +1 -1
  46. package/dist/layout/container.d.ts +1 -1
  47. package/dist/layout/container.js +6 -3
  48. package/dist/layout/index.d.ts +3 -3
  49. package/dist/layout/index.js +3 -3
  50. package/dist/layout/pagination.js +1 -1
  51. package/dist/layout/sidebar.d.ts +23 -3
  52. package/dist/layout/sidebar.js +18 -7
  53. package/dist/midi/spectrum-visualizer.js +1 -1
  54. package/dist/midi/timeline-ruler.js +1 -1
  55. package/dist/overlays/command-palette.js +1 -1
  56. package/dist/overlays/dialog.js +1 -1
  57. package/dist/overlays/dropdown-menu.js +2 -2
  58. package/dist/overlays/hover-card.js +1 -1
  59. package/dist/overlays/index.js +1 -1
  60. package/dist/overlays/popover.js +1 -1
  61. package/dist/overlays/sheet.js +1 -1
  62. package/dist/overlays/toast.js +1 -1
  63. package/dist/primitives/index.d.ts +1 -1
  64. package/dist/primitives/index.js +1 -1
  65. package/dist/primitives/status-dot.d.ts +7 -0
  66. package/dist/primitives/status-dot.js +8 -1
  67. package/dist/styles.css +47 -1
  68. package/dist/system/density.d.ts +4 -1
  69. package/dist/system/density.js +12 -4
  70. package/dist/system/index.d.ts +2 -2
  71. package/dist/system/portal.d.ts +5 -5
  72. package/dist/system/portal.js +11 -6
  73. package/dist/system/prepaint.d.ts +15 -8
  74. package/dist/system/prepaint.js +26 -9
  75. package/dist/system/theme-provider.d.ts +36 -10
  76. package/dist/system/theme-provider.js +56 -18
  77. package/dist/system/use-density.d.ts +3 -3
  78. package/dist/system/use-density.js +16 -6
  79. package/dist/system/use-theme.d.ts +4 -2
  80. package/dist/system/use-theme.js +4 -2
  81. package/dist/theming/_presets.js +80 -5
  82. package/dist/theming/create-theme.js +13 -4
  83. package/package.json +4 -3
@@ -0,0 +1,167 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { MenuGlyph } from "./_glyphs.js";
4
+ import { Sidebar } from "../layout/sidebar.js";
5
+ import { CrossGlyph } from "../overlays/_glyphs.js";
6
+ import { Drawer } from "../overlays/drawer.js";
7
+ import { IconButton } from "../primitives/icon-button.js";
8
+ import * as React from "react";
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
+ //#region src/chrome/app-shell.tsx
11
+ const AppShellContext = React.createContext({
12
+ navOpen: false,
13
+ setNavOpen: () => {}
14
+ });
15
+ /**
16
+ * The console frame: title bar, navigation, work area, status bar — one
17
+ * viewport tall, with exactly one thing scrolling.
18
+ *
19
+ * Every console screen in this system is the same five parts in the same
20
+ * arrangement, and the value here is not the parts (they already exist as
21
+ * `TitleBar`, `Sidebar`, `Toolbar` and `StatusBar`) but the arithmetic between
22
+ * them: `h-dvh` on the frame rather than `min-h-dvh`, an unbroken `min-h-0`
23
+ * chain from the frame down to the scroller, `min-w-0` on the content column,
24
+ * and a scroller that is not the document. Get one of those wrong and the
25
+ * status bar leaves the screen or the navigation column gets shoved off the
26
+ * side; the shell exists so no page has to get them right again.
27
+ *
28
+ * The bars are ordinary children — `TitleBar` and `StatusBar` are already
29
+ * `shrink-0` and need no wrapper — so a shell reads as the page's outline.
30
+ *
31
+ * This is also the only component in the library with a viewport breakpoint:
32
+ * below `md` the navigation column becomes a drawer and a trigger appears in
33
+ * the bar. See {@link AppShellSidebar} for the ruling.
34
+ *
35
+ * @example
36
+ * <AppShell.Root>
37
+ * <TitleBar center="main-stage.cue" actions={<Chip tone="ok">live</Chip>}>
38
+ * <AppShell.NavTrigger />
39
+ * <Eyebrow>CUE++</Eyebrow>
40
+ * </TitleBar>
41
+ * <AppShell.Body>
42
+ * <AppShell.Sidebar aria-label="Console" drawerTitle="Console">
43
+ * <Sidebar.Section label="Show">
44
+ * <Sidebar.Item icon={List} active>Cues</Sidebar.Item>
45
+ * </Sidebar.Section>
46
+ * </AppShell.Sidebar>
47
+ * <AppShell.Content>
48
+ * <Toolbar.Root aria-label="Playback"><Toolbar.Button icon={Play}>Go</Toolbar.Button></Toolbar.Root>
49
+ * <AppShell.Scroller><Panel>…</Panel></AppShell.Scroller>
50
+ * </AppShell.Content>
51
+ * <AppShell.Aside aria-label="Inspector">…</AppShell.Aside>
52
+ * </AppShell.Body>
53
+ * <StatusBar><StatusBar.Item tone="ok">Art-Net</StatusBar.Item></StatusBar>
54
+ * </AppShell.Root>
55
+ */
56
+ const AppShell = {
57
+ Root: React.forwardRef(function AppShellRoot({ className, navOpen: navOpenProp, defaultNavOpen = false, onNavOpenChange, ...elementProps }, ref) {
58
+ const [uncontrolled, setUncontrolled] = React.useState(defaultNavOpen);
59
+ const navOpen = navOpenProp ?? uncontrolled;
60
+ const setNavOpen = React.useCallback((open) => {
61
+ if (navOpenProp === void 0) setUncontrolled(open);
62
+ onNavOpenChange?.(open);
63
+ }, [navOpenProp, onNavOpenChange]);
64
+ const context = React.useMemo(() => ({
65
+ navOpen,
66
+ setNavOpen
67
+ }), [navOpen, setNavOpen]);
68
+ return /* @__PURE__ */ jsx(AppShellContext.Provider, {
69
+ value: context,
70
+ children: /* @__PURE__ */ jsx("div", {
71
+ ref,
72
+ "data-slot": "app-shell",
73
+ className: cn("relative flex h-dvh flex-col overflow-clip bg-bg text-fg", className),
74
+ ...elementProps
75
+ })
76
+ });
77
+ }),
78
+ Body: React.forwardRef(function AppShellBody({ className, ...elementProps }, ref) {
79
+ return /* @__PURE__ */ jsx("div", {
80
+ ref,
81
+ "data-slot": "app-shell-body",
82
+ className: cn("flex min-h-0 min-w-0 flex-1", className),
83
+ ...elementProps
84
+ });
85
+ }),
86
+ Sidebar: React.forwardRef(function AppShellSidebar({ className, drawerClassName, drawerTitle = "Navigation", closeLabel = "Close navigation", collapsible = true, landmark = "navigation", "aria-label": ariaLabel = "Navigation", children, ...sidebarProps }, ref) {
87
+ const { navOpen, setNavOpen } = React.useContext(AppShellContext);
88
+ const DrawerLandmark = landmark === "complementary" ? "aside" : "nav";
89
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Sidebar.Root, {
90
+ ref,
91
+ "data-slot": "app-shell-sidebar",
92
+ "aria-label": ariaLabel,
93
+ landmark,
94
+ className: cn("hidden md:flex", className),
95
+ ...sidebarProps,
96
+ children: [children, collapsible ? /* @__PURE__ */ jsx(Sidebar.Rail, {}) : null]
97
+ }), /* @__PURE__ */ jsx(Drawer.Root, {
98
+ swipeDirection: "left",
99
+ open: navOpen,
100
+ onOpenChange: setNavOpen,
101
+ children: /* @__PURE__ */ jsxs(Drawer.Content, {
102
+ grabHandle: false,
103
+ className: cn("w-[min(18rem,calc(100vw-3rem))]", drawerClassName),
104
+ children: [/* @__PURE__ */ jsxs("div", {
105
+ className: "flex shrink-0 items-center justify-between gap-(--cue-space-3)",
106
+ children: [/* @__PURE__ */ jsx(Drawer.Title, { children: drawerTitle }), /* @__PURE__ */ jsx(Drawer.Close, { render: /* @__PURE__ */ jsx(IconButton, {
107
+ "aria-label": closeLabel,
108
+ icon: CrossGlyph,
109
+ size: "sm"
110
+ }) })]
111
+ }), /* @__PURE__ */ jsx(DrawerLandmark, {
112
+ "data-slot": "app-shell-nav-drawer",
113
+ "aria-label": ariaLabel,
114
+ className: "flex min-h-0 flex-1 flex-col gap-(--cue-space-4) overflow-y-auto",
115
+ children
116
+ })]
117
+ })
118
+ })] });
119
+ }),
120
+ NavTrigger: React.forwardRef(function AppShellNavTrigger({ className, label = "Open navigation", icon = MenuGlyph, size = "md", onClick, ...elementProps }, ref) {
121
+ const { navOpen, setNavOpen } = React.useContext(AppShellContext);
122
+ return /* @__PURE__ */ jsx(IconButton, {
123
+ ref,
124
+ "data-slot": "app-shell-nav-trigger",
125
+ "aria-label": label,
126
+ "aria-expanded": navOpen,
127
+ icon,
128
+ size,
129
+ className: cn("shrink-0 md:hidden", className),
130
+ onClick: (event) => {
131
+ onClick?.(event);
132
+ if (!event.defaultPrevented) setNavOpen(true);
133
+ },
134
+ ...elementProps
135
+ });
136
+ }),
137
+ Content: React.forwardRef(function AppShellContent({ className, main = true, ...elementProps }, ref) {
138
+ return /* @__PURE__ */ jsx(main ? "main" : "div", {
139
+ ref,
140
+ "data-slot": "app-shell-content",
141
+ className: cn("flex min-h-0 min-w-0 flex-1 flex-col", className),
142
+ ...elementProps
143
+ });
144
+ }),
145
+ Scroller: React.forwardRef(function AppShellScroller({ className, padded = true, ...elementProps }, ref) {
146
+ return /* @__PURE__ */ jsx("div", {
147
+ ref,
148
+ "data-slot": "app-shell-scroller",
149
+ className: cn("min-h-0 min-w-0 flex-1 overflow-y-auto", padded ? "p-(--cue-space-5)" : null, className),
150
+ ...elementProps
151
+ });
152
+ }),
153
+ Aside: React.forwardRef(function AppShellAside({ className, width = "18rem", style, ...elementProps }, ref) {
154
+ return /* @__PURE__ */ jsx("aside", {
155
+ ref,
156
+ "data-slot": "app-shell-aside",
157
+ className: cn("flex shrink-0 flex-col gap-(--cue-space-4) overflow-y-auto border-l border-border bg-surface-1 p-(--cue-space-4)", className),
158
+ style: {
159
+ width,
160
+ ...style
161
+ },
162
+ ...elementProps
163
+ });
164
+ })
165
+ };
166
+ //#endregion
167
+ export { AppShell };
@@ -0,0 +1,75 @@
1
+ import { ContainerWidth } from "../layout/container.js";
2
+ import * as React from "react";
3
+ //#region src/chrome/footer.d.ts
4
+ interface FooterProps extends React.ComponentPropsWithoutRef<"footer"> {
5
+ /**
6
+ * Which measure the footer's column takes. Defaults to `"broad"`.
7
+ *
8
+ * The same four rungs `Container` and `PageShell` offer, so a footer lines up
9
+ * with the page above it by naming the same width — `broad` rather than
10
+ * `narrow` by default because a footer is usually the widest thing on a page,
11
+ * and a row of columns inside a 42rem reading measure is four squeezed lists.
12
+ */
13
+ width?: ContainerWidth;
14
+ /**
15
+ * The fine print, under a hairline of its own: copyright, a licence, a build
16
+ * number.
17
+ *
18
+ * A slot rather than a component, because that row is a sentence and a couple
19
+ * of links — but it is a *separated* sentence, and hand-rolling the rule and
20
+ * the space above it in every app is exactly the kind of repetition this
21
+ * component exists to end.
22
+ */
23
+ meta?: React.ReactNode;
24
+ /** Extra classes for the centred column inside the landmark. */
25
+ columnClassName?: string;
26
+ /** The footer's body — normally a `Grid` of `Stack`s of `Link`s. */
27
+ children?: React.ReactNode;
28
+ }
29
+ /**
30
+ * The `contentinfo` landmark at the bottom of a document: a hairline, a
31
+ * measure, and room for whatever a page ends with.
32
+ *
33
+ * Deliberately **not** a link-list component. A "complex footer" in this system
34
+ * is `Grid` + `Stack` + `Link` inside this one — components that already exist
35
+ * and already carry the density ladder — and a `Grid` with `minItemWidth` fills
36
+ * the row with as many columns as fit, so the footer is responsive without a
37
+ * breakpoint and without a prop for how many columns to draw. What is left over
38
+ * is what a footer actually needs and nothing else has: the landmark, the rule
39
+ * that separates it from the page, the measure that lines it up with the page,
40
+ * and the second rule above the fine print.
41
+ *
42
+ * The band is full-bleed and the column inside it is centred, which is why the
43
+ * hairline runs edge to edge while the links stop at the measure. The column is
44
+ * set in the bar's register — mono, at the UI size — because a footer is
45
+ * furniture. It paints no ground of its own: a footer is the end of the page,
46
+ * not a bar, and the seam is the hairline. Give it `className="bg-surface-1"`
47
+ * where a distinct band is wanted.
48
+ *
49
+ * Inside `AppShell` this is a child of `AppShell.Scroller`, at the end of the
50
+ * content column — so it scrolls with the page and its full bleed is the
51
+ * content column's width, not the window's. A footer placed *beside* the
52
+ * navigation column instead (after `AppShell.Body`) runs under the navigation
53
+ * and reads as chrome it is not. It coexists with `StatusBar`, which is the
54
+ * opposite object: a strip of the frame, always on screen, never scrolled.
55
+ *
56
+ * Static markup — no `"use client"`.
57
+ *
58
+ * @example
59
+ * <Footer meta={<><span>© 2026 CUE++</span><Link href="/legal">Legal</Link></>}>
60
+ * <Grid minItemWidth="12rem" gap={5}>
61
+ * <Stack gap={2}>
62
+ * <Eyebrow>Docs</Eyebrow>
63
+ * <Link href="/docs">Getting started</Link>
64
+ * <Link href="/docs/theming">Theming</Link>
65
+ * </Stack>
66
+ * <Stack gap={2}>
67
+ * <Eyebrow>Surfaces</Eyebrow>
68
+ * <Link href="/kitchen-sink">Kitchen sink</Link>
69
+ * </Stack>
70
+ * </Grid>
71
+ * </Footer>
72
+ */
73
+ declare const Footer: React.ForwardRefExoticComponent<FooterProps & React.RefAttributes<HTMLElement>>;
74
+ //#endregion
75
+ export { Footer, FooterProps };
@@ -0,0 +1,68 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import { Container } from "../layout/container.js";
3
+ import * as React from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/chrome/footer.tsx
6
+ /**
7
+ * The `contentinfo` landmark at the bottom of a document: a hairline, a
8
+ * measure, and room for whatever a page ends with.
9
+ *
10
+ * Deliberately **not** a link-list component. A "complex footer" in this system
11
+ * is `Grid` + `Stack` + `Link` inside this one — components that already exist
12
+ * and already carry the density ladder — and a `Grid` with `minItemWidth` fills
13
+ * the row with as many columns as fit, so the footer is responsive without a
14
+ * breakpoint and without a prop for how many columns to draw. What is left over
15
+ * is what a footer actually needs and nothing else has: the landmark, the rule
16
+ * that separates it from the page, the measure that lines it up with the page,
17
+ * and the second rule above the fine print.
18
+ *
19
+ * The band is full-bleed and the column inside it is centred, which is why the
20
+ * hairline runs edge to edge while the links stop at the measure. The column is
21
+ * set in the bar's register — mono, at the UI size — because a footer is
22
+ * furniture. It paints no ground of its own: a footer is the end of the page,
23
+ * not a bar, and the seam is the hairline. Give it `className="bg-surface-1"`
24
+ * where a distinct band is wanted.
25
+ *
26
+ * Inside `AppShell` this is a child of `AppShell.Scroller`, at the end of the
27
+ * content column — so it scrolls with the page and its full bleed is the
28
+ * content column's width, not the window's. A footer placed *beside* the
29
+ * navigation column instead (after `AppShell.Body`) runs under the navigation
30
+ * and reads as chrome it is not. It coexists with `StatusBar`, which is the
31
+ * opposite object: a strip of the frame, always on screen, never scrolled.
32
+ *
33
+ * Static markup — no `"use client"`.
34
+ *
35
+ * @example
36
+ * <Footer meta={<><span>© 2026 CUE++</span><Link href="/legal">Legal</Link></>}>
37
+ * <Grid minItemWidth="12rem" gap={5}>
38
+ * <Stack gap={2}>
39
+ * <Eyebrow>Docs</Eyebrow>
40
+ * <Link href="/docs">Getting started</Link>
41
+ * <Link href="/docs/theming">Theming</Link>
42
+ * </Stack>
43
+ * <Stack gap={2}>
44
+ * <Eyebrow>Surfaces</Eyebrow>
45
+ * <Link href="/kitchen-sink">Kitchen sink</Link>
46
+ * </Stack>
47
+ * </Grid>
48
+ * </Footer>
49
+ */
50
+ const Footer = React.forwardRef(function Footer({ className, columnClassName, width = "broad", meta, children, ...elementProps }, ref) {
51
+ return /* @__PURE__ */ jsx("footer", {
52
+ ref,
53
+ "data-slot": "footer",
54
+ className: cn("w-full border-t border-border", className),
55
+ ...elementProps,
56
+ children: /* @__PURE__ */ jsxs(Container, {
57
+ width,
58
+ className: cn("flex flex-col gap-(--cue-space-6) py-(--cue-space-7) font-mono text-(length:--cue-text-ui)", columnClassName),
59
+ children: [children, meta === void 0 ? null : /* @__PURE__ */ jsx("div", {
60
+ "data-slot": "footer-meta",
61
+ className: "flex flex-wrap items-center justify-between gap-(--cue-space-3) border-t border-border pt-(--cue-space-5) font-mono text-(length:--cue-text-label) text-fg-subtle",
62
+ children: meta
63
+ })]
64
+ })
65
+ });
66
+ });
67
+ //#endregion
68
+ export { Footer };
@@ -1,7 +1,11 @@
1
1
  import { ScrollEdges, TAB_FADE_PX, computeScrollEdges, edgeFadeMask } from "./_tabs-scroll.js";
2
+ import { BarOverflow } from "./_edge-scroller.js";
2
3
  import { TREE_GUIDE_GLYPHS, TreeGuide, TreeItem, TreeRow, flattenTree } from "./_tree.js";
4
+ import { AppBar, AppBarActionsProps, AppBarBrandProps, AppBarLinkProps, AppBarNavProps, AppBarRegister, AppBarRootProps, AppBarSeparatorProps } from "./app-bar.js";
5
+ import { AppShell, AppShellAsideProps, AppShellBodyProps, AppShellContentProps, AppShellNavTriggerProps, AppShellRootProps, AppShellScrollerProps, AppShellSidebarProps } from "./app-shell.js";
3
6
  import { EmptyState, EmptyStateProps } from "./empty-state.js";
4
7
  import { Eyebrow, EyebrowProps } from "./eyebrow.js";
8
+ import { Footer, FooterProps } from "./footer.js";
5
9
  import { NavigationMenu, NavigationMenuContentProps, NavigationMenuItemProps, NavigationMenuLinkProps, NavigationMenuListProps, NavigationMenuRootProps, NavigationMenuTriggerProps, NavigationMenuViewportProps } from "./navigation-menu.js";
6
10
  import { PageShell, PageShellProps, PageWidth } from "./page-shell.js";
7
11
  import { Panel, PanelHeader, PanelHeaderProps, PanelProps } from "./panel.js";
@@ -14,4 +18,4 @@ import { ScrollableTabsList, ScrollableTabsListProps, Tabs, TabsListProps, TabsP
14
18
  import { TitleBar, TitleBarProps } from "./title-bar.js";
15
19
  import { Tree, TreeProps, TreeVisibility, TreeVisibilityToggle, TreeVisibilityToggleProps } from "./tree.js";
16
20
  import { Toolbar, ToolbarButtonProps, ToolbarGroupProps, ToolbarRootProps, ToolbarSeparatorProps } from "./toolbar.js";
17
- export { EmptyState, type EmptyStateProps, Eyebrow, type EyebrowProps, NavigationMenu, type NavigationMenuContentProps, type NavigationMenuItemProps, type NavigationMenuLinkProps, type NavigationMenuListProps, type NavigationMenuRootProps, type NavigationMenuTriggerProps, type NavigationMenuViewportProps, PageShell, type PageShellProps, type PageWidth, Panel, PanelHeader, type PanelHeaderProps, type PanelProps, Row, type RowProps, ScrollArea, type ScrollAreaOrientation, type ScrollAreaProps, type ScrollEdges, ScrollableTabsList, type ScrollableTabsListProps, SeamList, type SeamListProps, SectionHeader, type SectionHeaderProps, SegmentedControl, type SegmentedControlItem, type SegmentedControlProps, type SegmentedControlSize, StatusBar, type StatusBarItemProps, type StatusBarProps, type StatusBarTone, TAB_FADE_PX, TREE_GUIDE_GLYPHS, Tabs, type TabsListProps, type TabsPanelProps, type TabsRootProps, type TabsTabProps, TitleBar, type TitleBarProps, Toolbar, type ToolbarButtonProps, type ToolbarGroupProps, type ToolbarRootProps, type ToolbarSeparatorProps, Tree, type TreeGuide, type TreeItem, type TreeProps, type TreeRow, type TreeVisibility, TreeVisibilityToggle, type TreeVisibilityToggleProps, computeScrollEdges, edgeFadeMask, flattenTree, rowVariants };
21
+ export { AppBar, type AppBarActionsProps, type AppBarBrandProps, type AppBarLinkProps, type AppBarNavProps, type AppBarRegister, type AppBarRootProps, type AppBarSeparatorProps, AppShell, type AppShellAsideProps, type AppShellBodyProps, type AppShellContentProps, type AppShellNavTriggerProps, type AppShellRootProps, type AppShellScrollerProps, type AppShellSidebarProps, type BarOverflow, EmptyState, type EmptyStateProps, Eyebrow, type EyebrowProps, Footer, type FooterProps, NavigationMenu, type NavigationMenuContentProps, type NavigationMenuItemProps, type NavigationMenuLinkProps, type NavigationMenuListProps, type NavigationMenuRootProps, type NavigationMenuTriggerProps, type NavigationMenuViewportProps, PageShell, type PageShellProps, type PageWidth, Panel, PanelHeader, type PanelHeaderProps, type PanelProps, Row, type RowProps, ScrollArea, type ScrollAreaOrientation, type ScrollAreaProps, type ScrollEdges, ScrollableTabsList, type ScrollableTabsListProps, SeamList, type SeamListProps, SectionHeader, type SectionHeaderProps, SegmentedControl, type SegmentedControlItem, type SegmentedControlProps, type SegmentedControlSize, StatusBar, type StatusBarItemProps, type StatusBarProps, type StatusBarTone, TAB_FADE_PX, TREE_GUIDE_GLYPHS, Tabs, type TabsListProps, type TabsPanelProps, type TabsRootProps, type TabsTabProps, TitleBar, type TitleBarProps, Toolbar, type ToolbarButtonProps, type ToolbarGroupProps, type ToolbarRootProps, type ToolbarSeparatorProps, Tree, type TreeGuide, type TreeItem, type TreeProps, type TreeRow, type TreeVisibility, TreeVisibilityToggle, type TreeVisibilityToggleProps, computeScrollEdges, edgeFadeMask, flattenTree, rowVariants };
@@ -1,7 +1,10 @@
1
1
  import { TAB_FADE_PX, computeScrollEdges, edgeFadeMask } from "./_tabs-scroll.js";
2
2
  import { TREE_GUIDE_GLYPHS, flattenTree } from "./_tree.js";
3
+ import { AppBar } from "./app-bar.js";
4
+ import { AppShell } from "./app-shell.js";
3
5
  import { EmptyState } from "./empty-state.js";
4
6
  import { Eyebrow } from "./eyebrow.js";
7
+ import { Footer } from "./footer.js";
5
8
  import { NavigationMenu } from "./navigation-menu.js";
6
9
  import { PageShell } from "./page-shell.js";
7
10
  import { Panel, PanelHeader } from "./panel.js";
@@ -14,4 +17,4 @@ import { ScrollableTabsList, Tabs } from "./tabs.js";
14
17
  import { TitleBar } from "./title-bar.js";
15
18
  import { Tree, TreeVisibilityToggle } from "./tree.js";
16
19
  import { Toolbar } from "./toolbar.js";
17
- export { EmptyState, Eyebrow, NavigationMenu, PageShell, Panel, PanelHeader, Row, ScrollArea, ScrollableTabsList, SeamList, SectionHeader, SegmentedControl, StatusBar, TAB_FADE_PX, TREE_GUIDE_GLYPHS, Tabs, TitleBar, Toolbar, Tree, TreeVisibilityToggle, computeScrollEdges, edgeFadeMask, flattenTree, rowVariants };
20
+ export { AppBar, AppShell, EmptyState, Eyebrow, Footer, NavigationMenu, PageShell, Panel, PanelHeader, Row, ScrollArea, ScrollableTabsList, SeamList, SectionHeader, SegmentedControl, StatusBar, TAB_FADE_PX, TREE_GUIDE_GLYPHS, Tabs, TitleBar, Toolbar, Tree, TreeVisibilityToggle, computeScrollEdges, edgeFadeMask, flattenTree, rowVariants };
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
+ import { CaretDownGlyph } from "./_glyphs.js";
3
4
  import { useCuePortalProps } from "../system/portal.js";
4
5
  import { overlayPopupTransitionClasses, overlayPositionerClasses, overlaySurfaceClasses } from "../overlays/_surface.js";
5
- import { CaretDownGlyph } from "./_glyphs.js";
6
6
  import * as React from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { NavigationMenu } from "@base-ui/react/navigation-menu";
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  //#region src/chrome/page-shell.d.ts
3
3
  /** How wide the content column is allowed to get. */
4
- type PageWidth = "narrow" | "wide";
4
+ type PageWidth = "narrow" | "wide" | "broad" | "full";
5
5
  interface PageShellProps extends Omit<React.ComponentPropsWithoutRef<"div">, "title"> {
6
6
  /** Which measure the content column takes. Defaults to `"narrow"`. */
7
7
  width?: PageWidth;
@@ -3,14 +3,24 @@ import * as React from "react";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
  //#region src/chrome/page-shell.tsx
5
5
  /**
6
- * Two measures, not a scale. `narrow` (42rem) is a reading column — settings,
7
- * forms, prose. `wide` (54rem) is a working column — tables and panel grids.
8
- * Anything that wants the full viewport is not a page shell; it is a console
9
- * layout and should own its own frame.
6
+ * Four measures, not a scale — and the same four `Container` offers.
7
+ *
8
+ * `narrow` (42rem) is a reading column: settings, forms, prose. `wide` (54rem)
9
+ * is a working column: tables and panel grids. `broad` (84rem) is the
10
+ * application measure — a two-column documentation page, a page whose body is a
11
+ * device frame — and it exists because both of this repo's own hard-coded
12
+ * `max-w-[84rem]` overrides were asking for it. `full` caps nothing, for a page
13
+ * whose body sets its own width.
14
+ *
15
+ * `full` is still a page shell, not a console layout: it keeps the gutters and
16
+ * the standard page header. A screen that wants the *viewport* — one frame tall,
17
+ * with its own scroller — is `AppShell`, not this.
10
18
  */
11
19
  const WIDTHS = {
12
20
  narrow: "max-w-[42rem]",
13
- wide: "max-w-[54rem]"
21
+ wide: "max-w-[54rem]",
22
+ broad: "max-w-[84rem]",
23
+ full: ""
14
24
  };
15
25
  /**
16
26
  * A centred content column with a standard page header.
@@ -1,3 +1,4 @@
1
+ import { BarOverflow } from "./_edge-scroller.js";
1
2
  import * as React from "react";
2
3
  import { ToggleGroup } from "@base-ui/react/toggle-group";
3
4
  //#region src/chrome/segmented-control.d.ts
@@ -29,6 +30,24 @@ interface SegmentedControlProps extends Omit<ToggleGroup.Props<string>, "value"
29
30
  className?: string;
30
31
  /** Extra classes for every segment. */
31
32
  itemClassName?: string;
33
+ /**
34
+ * What the control does when its segments do not fit. Defaults to `"scroll"`.
35
+ *
36
+ * The same behaviour `Toolbar` has, from the same module, for the same
37
+ * reason: a switcher with five segments in a page header's actions slot runs
38
+ * off a phone-width screen, and wrapping it would turn a one-rung control
39
+ * into a two-row block that no longer reads as one switch. `"scroll"` keeps
40
+ * the rim and the height, scrolls the segments inside them, fades the
41
+ * overflowing edge and offers a pager chevron there.
42
+ *
43
+ * `"clip"` is the old behaviour, for a control already inside something that
44
+ * scrolls.
45
+ */
46
+ overflow?: BarOverflow;
47
+ /** Accessible name for the chevron that reveals segments off the leading edge. */
48
+ previousLabel?: string;
49
+ /** Accessible name for the chevron that reveals segments off the trailing edge. */
50
+ nextLabel?: string;
32
51
  }
33
52
  /**
34
53
  * A one-of-N switch drawn as a row of segments: the console's view picker.
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
+ import { useComposedRefs } from "../lib/compose.js";
4
+ import { EDGE_SCROLLER_CLASSES, EdgeChevrons, useEdgeScroll } from "./_edge-scroller.js";
3
5
  import * as React from "react";
4
6
  import { jsx, jsxs } from "react/jsx-runtime";
5
7
  import { Toggle } from "@base-ui/react/toggle";
@@ -40,9 +42,12 @@ const ITEM_CLASSES = "inline-flex shrink-0 cursor-pointer select-none items-cent
40
42
  * items={[{ value: "beams", label: "Beams" }, { value: "patch", label: "Patch" }]}
41
43
  * />
42
44
  */
43
- const SegmentedControl = React.forwardRef(function SegmentedControl({ items, value: valueProp, defaultValue, onValueChange, size = "md", className, itemClassName, ...groupProps }, ref) {
45
+ const SegmentedControl = React.forwardRef(function SegmentedControl({ items, value: valueProp, defaultValue, onValueChange, size = "md", className, itemClassName, overflow = "scroll", previousLabel = "Show earlier segments", nextLabel = "Show later segments", ...groupProps }, ref) {
44
46
  const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
45
47
  const value = valueProp ?? uncontrolledValue;
48
+ const scrolls = overflow === "scroll";
49
+ const scroll = useEdgeScroll(scrolls);
50
+ const groupRef = useComposedRefs(ref, scroll.ref);
46
51
  const handleValueChange = (groupValue) => {
47
52
  const next = groupValue[0];
48
53
  if (next === void 0 || next === value) return;
@@ -50,26 +55,40 @@ const SegmentedControl = React.forwardRef(function SegmentedControl({ items, val
50
55
  onValueChange?.(next);
51
56
  };
52
57
  const iconSize = size === "sm" ? "size-icon-sm" : "size-icon-md";
53
- return /* @__PURE__ */ jsx(ToggleGroup, {
54
- ref,
55
- value: value === void 0 ? [] : [value],
56
- onValueChange: handleValueChange,
57
- "data-slot": "segmented-control",
58
- className: cn("inline-flex items-center gap-(--cue-space-1) rounded-(--radius-control) border border-border bg-surface-1 p-(--cue-space-1)", className),
59
- ...groupProps,
60
- children: items.map((item) => {
61
- const Icon = item.icon;
62
- return /* @__PURE__ */ jsxs(Toggle, {
63
- value: item.value,
64
- disabled: item.disabled,
65
- "data-slot": "segmented-control-item",
66
- className: cn(ITEM_CLASSES, ITEM_SIZES[size], itemClassName),
67
- children: [Icon === void 0 ? null : /* @__PURE__ */ jsx(Icon, {
68
- "aria-hidden": "true",
69
- className: cn("shrink-0", iconSize)
70
- }), item.label]
71
- }, item.value);
72
- })
58
+ return /* @__PURE__ */ jsxs("div", {
59
+ "data-slot": "segmented-control-frame",
60
+ className: cn("relative inline-flex max-w-full min-w-0 shrink-0 items-center rounded-(--radius-control) border border-border bg-surface-1 p-(--cue-space-1)", className),
61
+ children: [/* @__PURE__ */ jsx(ToggleGroup, {
62
+ ref: groupRef,
63
+ value: value === void 0 ? [] : [value],
64
+ onValueChange: handleValueChange,
65
+ "data-slot": "segmented-control",
66
+ className: cn("inline-flex min-w-0 items-center gap-(--cue-space-1)", scrolls ? EDGE_SCROLLER_CLASSES : null),
67
+ ...groupProps,
68
+ style: {
69
+ ...groupProps.style,
70
+ ...scroll.style
71
+ },
72
+ children: items.map((item) => {
73
+ const Icon = item.icon;
74
+ return /* @__PURE__ */ jsxs(Toggle, {
75
+ value: item.value,
76
+ disabled: item.disabled,
77
+ "data-slot": "segmented-control-item",
78
+ className: cn(ITEM_CLASSES, ITEM_SIZES[size], itemClassName),
79
+ children: [Icon === void 0 ? null : /* @__PURE__ */ jsx(Icon, {
80
+ "aria-hidden": "true",
81
+ className: cn("shrink-0", iconSize)
82
+ }), item.label]
83
+ }, item.value);
84
+ })
85
+ }), /* @__PURE__ */ jsx(EdgeChevrons, {
86
+ edges: scroll.edges,
87
+ onNudge: scroll.nudge,
88
+ previousLabel,
89
+ nextLabel,
90
+ className: "rounded-(--radius-control) bg-surface-1"
91
+ })]
73
92
  });
74
93
  });
75
94
  //#endregion
@@ -1,7 +1,6 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
- import { computeScrollEdges, edgeFadeMask } from "./_tabs-scroll.js";
4
- import { ChevronLeftGlyph, ChevronRightGlyph } from "./_glyphs.js";
3
+ import { EDGE_SCROLLER_CLASSES, EdgeChevrons, useEdgeScroll } from "./_edge-scroller.js";
5
4
  import * as React from "react";
6
5
  import { jsx, jsxs } from "react/jsx-runtime";
7
6
  import { Tabs } from "@base-ui/react/tabs";
@@ -52,8 +51,6 @@ const TabsPanel = React.forwardRef(function TabsPanel({ className, ...elementPro
52
51
  ...elementProps
53
52
  });
54
53
  });
55
- /** Page the strip by most of a screenful, keeping a sliver of context. */
56
- const NUDGE_FRACTION = .7;
57
54
  /**
58
55
  * A {@link TabsList} that scrolls when its tabs do not fit, and says so.
59
56
  *
@@ -75,75 +72,34 @@ const NUDGE_FRACTION = .7;
75
72
  * </Tabs.Root>
76
73
  */
77
74
  const ScrollableTabsList = React.forwardRef(function ScrollableTabsList({ className, containerClassName, activeValue, children, ...listProps }, ref) {
78
- const scrollerRef = React.useRef(null);
79
- const [edges, setEdges] = React.useState({
80
- start: false,
81
- end: false
82
- });
83
- const sync = React.useCallback(() => {
84
- const el = scrollerRef.current;
85
- if (el === null) return;
86
- setEdges(computeScrollEdges(el.scrollLeft, el.scrollWidth, el.clientWidth));
87
- }, []);
88
- React.useEffect(() => {
89
- const el = scrollerRef.current;
90
- if (el === null) return;
91
- sync();
92
- el.addEventListener("scroll", sync, { passive: true });
93
- const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => sync());
94
- observer?.observe(el);
95
- if (el.firstElementChild !== null) observer?.observe(el.firstElementChild);
96
- return () => {
97
- el.removeEventListener("scroll", sync);
98
- observer?.disconnect();
99
- };
100
- }, [sync]);
75
+ const scroll = useEdgeScroll();
101
76
  React.useEffect(() => {
102
- scrollerRef.current?.querySelector("[data-slot='tabs-tab'][data-active]")?.scrollIntoView?.({
77
+ scroll.ref.current?.querySelector("[data-slot='tabs-tab'][data-active]")?.scrollIntoView?.({
103
78
  block: "nearest",
104
79
  inline: "nearest"
105
80
  });
106
81
  }, [activeValue]);
107
- const nudge = (direction) => {
108
- const el = scrollerRef.current;
109
- el?.scrollBy?.({
110
- left: direction * el.clientWidth * NUDGE_FRACTION,
111
- behavior: "smooth"
112
- });
113
- };
114
- const mask = edgeFadeMask(edges);
115
- const chevronClasses = "absolute inset-y-0 z-10 flex w-(--cue-space-5) cursor-pointer items-center justify-center bg-surface-2 text-fg-muted outline-none hover:text-fg focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-accent";
116
82
  return /* @__PURE__ */ jsxs("div", {
117
83
  "data-slot": "tabs-scroll-frame",
118
84
  className: cn("relative min-w-0", containerClassName),
119
- children: [
120
- /* @__PURE__ */ jsx("div", {
121
- ref: scrollerRef,
122
- "data-slot": "tabs-scroller",
123
- className: "overflow-x-auto [mask-image:var(--cue-tabs-fade)] [-webkit-mask-image:var(--cue-tabs-fade)] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
124
- style: mask === void 0 ? void 0 : { "--cue-tabs-fade": mask },
125
- children: /* @__PURE__ */ jsx(TabsList, {
126
- ref,
127
- className: cn("w-max", className),
128
- ...listProps,
129
- children
130
- })
131
- }),
132
- edges.start ? /* @__PURE__ */ jsx("button", {
133
- type: "button",
134
- "aria-label": "Show earlier tabs",
135
- onClick: () => nudge(-1),
136
- className: cn(chevronClasses, "left-0"),
137
- children: /* @__PURE__ */ jsx(ChevronLeftGlyph, { className: "size-icon-sm" })
138
- }) : null,
139
- edges.end ? /* @__PURE__ */ jsx("button", {
140
- type: "button",
141
- "aria-label": "Show later tabs",
142
- onClick: () => nudge(1),
143
- className: cn(chevronClasses, "right-0"),
144
- children: /* @__PURE__ */ jsx(ChevronRightGlyph, { className: "size-icon-sm" })
145
- }) : null
146
- ]
85
+ children: [/* @__PURE__ */ jsx("div", {
86
+ ref: scroll.ref,
87
+ "data-slot": "tabs-scroller",
88
+ className: EDGE_SCROLLER_CLASSES,
89
+ style: scroll.style,
90
+ children: /* @__PURE__ */ jsx(TabsList, {
91
+ ref,
92
+ className: cn("w-max", className),
93
+ ...listProps,
94
+ children
95
+ })
96
+ }), /* @__PURE__ */ jsx(EdgeChevrons, {
97
+ edges: scroll.edges,
98
+ onNudge: scroll.nudge,
99
+ previousLabel: "Show earlier tabs",
100
+ nextLabel: "Show later tabs",
101
+ className: "bg-surface-2"
102
+ })]
147
103
  });
148
104
  });
149
105
  /**