@calliopelabs/ui 0.0.0-stage → 0.3.1
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/LICENSE +21 -0
- package/README.md +334 -2
- package/dist/appearance/AppearancePicker.d.ts +10 -0
- package/dist/appearance/AppearancePicker.js +75 -0
- package/dist/appearance/AppearanceProvider.d.ts +53 -0
- package/dist/appearance/AppearanceProvider.js +113 -0
- package/dist/appearance/AppearanceScript.d.ts +16 -0
- package/dist/appearance/AppearanceScript.js +14 -0
- package/dist/appearance/appearance.d.ts +90 -0
- package/dist/appearance/appearance.js +151 -0
- package/dist/appearance/boot.d.ts +30 -0
- package/dist/appearance/boot.js +91 -0
- package/dist/appearance/brands.d.ts +187 -0
- package/dist/appearance/brands.js +150 -0
- package/dist/appearance/product.d.ts +89 -0
- package/dist/appearance/product.js +96 -0
- package/dist/data/DataTable.d.ts +66 -0
- package/dist/data/DataTable.js +148 -0
- package/dist/data/List.d.ts +55 -0
- package/dist/data/List.js +41 -0
- package/dist/data/types.d.ts +92 -0
- package/dist/data/types.js +2 -0
- package/dist/data/use-row-selection.d.ts +17 -0
- package/dist/data/use-row-selection.js +51 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +61 -0
- package/dist/lib/cn.d.ts +3 -0
- package/dist/lib/cn.js +6 -0
- package/dist/lib/local-store.d.ts +12 -0
- package/dist/lib/local-store.js +54 -0
- package/dist/nav/Nav.d.ts +21 -0
- package/dist/nav/Nav.js +97 -0
- package/dist/nav/model.d.ts +77 -0
- package/dist/nav/model.js +72 -0
- package/dist/overlays/ConfirmDialog.d.ts +37 -0
- package/dist/overlays/ConfirmDialog.js +70 -0
- package/dist/overlays/Dialog.d.ts +18 -0
- package/dist/overlays/Dialog.js +39 -0
- package/dist/overlays/Drawer.d.ts +24 -0
- package/dist/overlays/Drawer.js +42 -0
- package/dist/page/Breadcrumbs.d.ts +20 -0
- package/dist/page/Breadcrumbs.js +25 -0
- package/dist/page/PageFrame.d.ts +17 -0
- package/dist/page/PageFrame.js +14 -0
- package/dist/page/PageHeader.d.ts +28 -0
- package/dist/page/PageHeader.js +13 -0
- package/dist/page/Section.d.ts +18 -0
- package/dist/page/Section.js +12 -0
- package/dist/page/TabNav.d.ts +19 -0
- package/dist/page/TabNav.js +16 -0
- package/dist/primitives/Button.d.ts +16 -0
- package/dist/primitives/Button.js +36 -0
- package/dist/primitives/Checkbox.d.ts +8 -0
- package/dist/primitives/Checkbox.js +13 -0
- package/dist/primitives/DropdownMenu.d.ts +11 -0
- package/dist/primitives/DropdownMenu.js +25 -0
- package/dist/primitives/Input.d.ts +2 -0
- package/dist/primitives/Input.js +5 -0
- package/dist/primitives/Popover.d.ts +5 -0
- package/dist/primitives/Popover.js +13 -0
- package/dist/primitives/Skeleton.d.ts +3 -0
- package/dist/primitives/Skeleton.js +6 -0
- package/dist/primitives/Tabs.d.ts +12 -0
- package/dist/primitives/Tabs.js +22 -0
- package/dist/provider/UIProvider.d.ts +29 -0
- package/dist/provider/UIProvider.js +21 -0
- package/dist/provider/messages.d.ts +106 -0
- package/dist/provider/messages.js +104 -0
- package/dist/shell/AppShell.d.ts +43 -0
- package/dist/shell/AppShell.js +62 -0
- package/dist/shell/BrandMark.d.ts +14 -0
- package/dist/shell/BrandMark.js +14 -0
- package/dist/shell/RailPanel.d.ts +39 -0
- package/dist/shell/RailPanel.js +62 -0
- package/dist/shell/RailTree.d.ts +35 -0
- package/dist/shell/RailTree.js +32 -0
- package/dist/shell/Sidebar.d.ts +26 -0
- package/dist/shell/Sidebar.js +21 -0
- package/dist/shell/UserMenu.d.ts +22 -0
- package/dist/shell/UserMenu.js +31 -0
- package/dist/shell/placement.d.ts +10 -0
- package/dist/shell/placement.js +6 -0
- package/dist/shell/rail-state.d.ts +13 -0
- package/dist/shell/rail-state.js +28 -0
- package/dist/states/EmptyState.d.ts +20 -0
- package/dist/states/EmptyState.js +15 -0
- package/dist/states/ErrorState.d.ts +23 -0
- package/dist/states/ErrorState.js +16 -0
- package/dist/states/LoadingState.d.ts +14 -0
- package/dist/states/LoadingState.js +14 -0
- package/dist/status/StatusBadge.d.ts +19 -0
- package/dist/status/StatusBadge.js +13 -0
- package/dist/status/StatusDot.d.ts +14 -0
- package/dist/status/StatusDot.js +7 -0
- package/dist/status/tones.d.ts +17 -0
- package/dist/status/tones.js +29 -0
- package/dist/styles/preset.css +175 -0
- package/dist/styles/theme.css +339 -0
- package/dist/theme/contrast.d.ts +8 -0
- package/dist/theme/contrast.js +18 -0
- package/dist/theme/css.d.ts +23 -0
- package/dist/theme/css.js +116 -0
- package/dist/theme/tokens.d.ts +195 -0
- package/dist/theme/tokens.js +294 -0
- package/package.json +112 -4
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { Skeleton } from "../primitives/Skeleton.js";
|
|
5
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
6
|
+
import { EmptyState } from "../states/EmptyState.js";
|
|
7
|
+
import { ErrorState } from "../states/ErrorState.js";
|
|
8
|
+
/**
|
|
9
|
+
* A short, fixed list (integrations, links, a summary's top five) in the
|
|
10
|
+
* person's density: compact is one panel of hairline rows; cards boxes each
|
|
11
|
+
* item and flows them into responsive columns. Six links with a name and a
|
|
12
|
+
* sentence are a list, not six cards, until someone asks for cards.
|
|
13
|
+
*
|
|
14
|
+
* The layout is driven by CSS variables that `data-density` sets, so the
|
|
15
|
+
* first paint already has the right one and a subtree can override it. A
|
|
16
|
+
* list that can grow without bound is a DataTable, with server paging.
|
|
17
|
+
*/
|
|
18
|
+
export function List({ label, items, getKey, renderItem, density, loading = false, loadingCount = 4, error, onRetry, empty, className, }) {
|
|
19
|
+
const { messages } = useUI();
|
|
20
|
+
const frame = "grid min-w-0 grid-cols-(--list-columns) gap-(--list-gap) overflow-hidden rounded-(--list-frame-radius) border-(length:--list-frame-border)";
|
|
21
|
+
const box = "relative min-w-0 rounded-(--list-item-radius) border-(length:--list-item-border) bg-(--list-item-bg) [padding:var(--list-item-pad)] not-first:border-t";
|
|
22
|
+
if (error) {
|
|
23
|
+
return (_jsx(ErrorState, { title: messages.table.loadFailed(label), error: error, onRetry: onRetry, className: className }));
|
|
24
|
+
}
|
|
25
|
+
if (loading) {
|
|
26
|
+
return (_jsxs("div", { "data-slot": "list", "data-density": density, "aria-busy": true, className: cn(frame, className), children: [_jsx("p", { role: "status", className: "sr-only", children: messages.states.loading }), Array.from({ length: loadingCount }, (_, i) => (_jsxs("div", { className: cn(box, "flex items-center gap-3"), children: [_jsx(Skeleton, { className: "size-4 shrink-0" }), _jsx(Skeleton, { className: "h-4 flex-1" })] }, i)))] }));
|
|
27
|
+
}
|
|
28
|
+
if (items.length === 0) {
|
|
29
|
+
return _jsx(EmptyState, { ...empty, className: className });
|
|
30
|
+
}
|
|
31
|
+
return (_jsx("ul", { "data-slot": "list", "data-density": density, "aria-label": label, className: cn(frame, className), children: items.map((item) => (_jsx("li", { className: box, children: renderItem(item) }, getKey(item)))) }));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* One item's content: a row in compact density (icon, title and description,
|
|
35
|
+
* then meta, status and actions on the right) and a card in cards density
|
|
36
|
+
* (the same parts stacked).
|
|
37
|
+
*/
|
|
38
|
+
export function ListItem({ title, href, description, icon, meta, status, actions, className, }) {
|
|
39
|
+
const { Link } = useUI();
|
|
40
|
+
return (_jsxs("div", { "data-slot": "list-item", className: cn("flex min-w-0 [flex-direction:var(--list-item-direction)] [align-items:var(--list-item-align)] gap-3", className), children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-start gap-3", children: [icon && (_jsx("span", { "aria-hidden": true, className: "mt-0.5 shrink-0 text-muted-foreground [&_svg]:size-4", children: icon })), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-sm font-medium", children: href ? (_jsx(Link, { href: href, className: "outline-none after:absolute after:inset-0 after:rounded-(--list-item-radius) hover:underline focus-visible:after:ring-2 focus-visible:after:ring-ring focus-visible:after:ring-inset", children: title })) : (title) }), description && (_jsx("p", { className: "mt-0.5 line-clamp-2 text-sm [overflow-wrap:anywhere] text-muted-foreground", children: description }))] })] }), (meta || status || actions) && (_jsxs("div", { className: "[margin-inline-start:var(--list-item-trail)] flex shrink-0 flex-wrap items-center gap-2", children: [meta && (_jsx("span", { className: "font-mono text-xs text-muted-foreground tabular-nums", children: meta })), status, actions && _jsx("span", { className: "relative z-10 flex items-center gap-1", children: actions })] }))] }));
|
|
41
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* What a table is showing. A union rather than a bag of booleans, so the
|
|
4
|
+
* render is a total switch: every "renders nothing" bug Astrolift's audit
|
|
5
|
+
* found was a missing branch.
|
|
6
|
+
*/
|
|
7
|
+
export type PageState = "loading" | "error"
|
|
8
|
+
/** No rows and no filter narrowing them: the list is genuinely empty. */
|
|
9
|
+
| "empty"
|
|
10
|
+
/** No rows because the search or filters excluded them all. */
|
|
11
|
+
| "emptyFiltered" | "ready";
|
|
12
|
+
export type SortDirection = "asc" | "desc";
|
|
13
|
+
export interface SortState {
|
|
14
|
+
key: string;
|
|
15
|
+
dir: SortDirection;
|
|
16
|
+
}
|
|
17
|
+
export interface Column<TRow> {
|
|
18
|
+
/** Stable identity; also the React key. */
|
|
19
|
+
id: string;
|
|
20
|
+
header: React.ReactNode;
|
|
21
|
+
/** Plain-text name where a node will not do. Defaults to `header` when that is a string. */
|
|
22
|
+
label?: string;
|
|
23
|
+
cell: (row: TRow) => React.ReactNode;
|
|
24
|
+
/** A Tailwind width class (`w-32`); it also sizes the column's loading skeleton. */
|
|
25
|
+
width?: string;
|
|
26
|
+
align?: "left" | "right" | "center";
|
|
27
|
+
headClassName?: string;
|
|
28
|
+
cellClassName?: string;
|
|
29
|
+
/**
|
|
30
|
+
* The server's sort key. Present, the header is a sort control; absent, a
|
|
31
|
+
* plain header. There is no client sort: reordering one page while the rest
|
|
32
|
+
* of the result set sits on the server is wrong at every page boundary.
|
|
33
|
+
*/
|
|
34
|
+
sortKey?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Copy for a genuinely empty list: what is missing, and how to change that. */
|
|
37
|
+
export interface EmptyStateSpec {
|
|
38
|
+
icon?: React.ReactNode;
|
|
39
|
+
title: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
actionHref?: string;
|
|
42
|
+
actionLabel?: string;
|
|
43
|
+
learnMoreHref?: string;
|
|
44
|
+
learnMoreLabel?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The data side of a DataTable: rows, state, paging, search and sort, with
|
|
48
|
+
* the server doing the work. The package fetches nothing, so each app
|
|
49
|
+
* implements this over its own data layer; Astrolift's `useCursorTable`
|
|
50
|
+
* (Apollo, keyset cursors, debounced server search) already satisfies it.
|
|
51
|
+
* Rules the type cannot hold: paging is server-side; the search box shows only
|
|
52
|
+
* when the query takes a search argument (`searchEnabled`); search is
|
|
53
|
+
* debounced by the controller, never per keystroke; rows from the previous
|
|
54
|
+
* question stay on screen (`isStale`, drawn with a progress line, at full
|
|
55
|
+
* contrast) until the next answer lands.
|
|
56
|
+
*/
|
|
57
|
+
export interface TableController<TRow> {
|
|
58
|
+
rows: readonly TRow[];
|
|
59
|
+
state: PageState;
|
|
60
|
+
error?: {
|
|
61
|
+
message: string;
|
|
62
|
+
} | null;
|
|
63
|
+
retry: () => void;
|
|
64
|
+
/** The exact count when the server knows it cheaply; null otherwise. */
|
|
65
|
+
totalCount: number | null;
|
|
66
|
+
/** 0-based index of the page on screen. */
|
|
67
|
+
pageIndex: number;
|
|
68
|
+
hasNext: boolean;
|
|
69
|
+
hasPrev: boolean;
|
|
70
|
+
next: () => void;
|
|
71
|
+
prev: () => void;
|
|
72
|
+
pageSize: number;
|
|
73
|
+
/** The page sizes offered. Defaults to 10, 25, 50 and 100. */
|
|
74
|
+
pageSizes?: readonly number[];
|
|
75
|
+
setPageSize: (size: number) => void;
|
|
76
|
+
/** The live input value: it updates on every keystroke; the query does not. */
|
|
77
|
+
search: string;
|
|
78
|
+
setSearch: (search: string) => void;
|
|
79
|
+
searchEnabled: boolean;
|
|
80
|
+
/** The rows on screen answer a previous question (a search still debouncing, a page in flight). */
|
|
81
|
+
isStale: boolean;
|
|
82
|
+
/** One key, or the ordered keys of a multi-key sort. */
|
|
83
|
+
sort: SortState | readonly SortState[] | undefined;
|
|
84
|
+
/** `additive` is true on shift-click: add the column as the next key. */
|
|
85
|
+
toggleSort: (key: string, additive: boolean) => void;
|
|
86
|
+
sortEnabled: boolean;
|
|
87
|
+
/** True when a search or filter is narrowing the result set. */
|
|
88
|
+
isFiltered: boolean;
|
|
89
|
+
clearFilters: () => void;
|
|
90
|
+
}
|
|
91
|
+
export declare const PAGE_SIZES: readonly number[];
|
|
92
|
+
export declare const DEFAULT_PAGE_SIZE = 25;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface RowSelection {
|
|
2
|
+
selectedIds: readonly string[];
|
|
3
|
+
selectedCount: number;
|
|
4
|
+
isSelected: (id: string) => boolean;
|
|
5
|
+
toggle: (id: string) => void;
|
|
6
|
+
/** Select, or clear, every row on the current page. */
|
|
7
|
+
togglePage: (ids: readonly string[]) => void;
|
|
8
|
+
/** `true`, `false` or `"indeterminate"`, for the header box. */
|
|
9
|
+
pageSelectionState: (ids: readonly string[]) => boolean | "indeterminate";
|
|
10
|
+
clear: () => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Row selection that survives paging (Astrolift #1231): the selected ids are
|
|
14
|
+
* kept across pages, so the count in the bulk bar means what it says, while
|
|
15
|
+
* the header box answers for the page on screen only.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useRowSelection(): RowSelection;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Row selection that survives paging (Astrolift #1231): the selected ids are
|
|
5
|
+
* kept across pages, so the count in the bulk bar means what it says, while
|
|
6
|
+
* the header box answers for the page on screen only.
|
|
7
|
+
*/
|
|
8
|
+
export function useRowSelection() {
|
|
9
|
+
const [selected, setSelected] = React.useState(() => new Set());
|
|
10
|
+
const toggle = React.useCallback((id) => {
|
|
11
|
+
setSelected((previous) => {
|
|
12
|
+
const next = new Set(previous);
|
|
13
|
+
if (!next.delete(id))
|
|
14
|
+
next.add(id);
|
|
15
|
+
return next;
|
|
16
|
+
});
|
|
17
|
+
}, []);
|
|
18
|
+
const togglePage = React.useCallback((ids) => {
|
|
19
|
+
setSelected((previous) => {
|
|
20
|
+
const next = new Set(previous);
|
|
21
|
+
const allOn = ids.length > 0 && ids.every((id) => next.has(id));
|
|
22
|
+
for (const id of ids) {
|
|
23
|
+
if (allOn)
|
|
24
|
+
next.delete(id);
|
|
25
|
+
else
|
|
26
|
+
next.add(id);
|
|
27
|
+
}
|
|
28
|
+
return next;
|
|
29
|
+
});
|
|
30
|
+
}, []);
|
|
31
|
+
const pageSelectionState = React.useCallback((ids) => {
|
|
32
|
+
if (ids.length === 0)
|
|
33
|
+
return false;
|
|
34
|
+
const on = ids.filter((id) => selected.has(id)).length;
|
|
35
|
+
if (on === 0)
|
|
36
|
+
return false;
|
|
37
|
+
return on === ids.length ? true : "indeterminate";
|
|
38
|
+
}, [selected]);
|
|
39
|
+
const isSelected = React.useCallback((id) => selected.has(id), [selected]);
|
|
40
|
+
const clear = React.useCallback(() => setSelected(new Set()), []);
|
|
41
|
+
const selectedIds = React.useMemo(() => [...selected], [selected]);
|
|
42
|
+
return {
|
|
43
|
+
selectedIds,
|
|
44
|
+
selectedCount: selected.size,
|
|
45
|
+
isSelected,
|
|
46
|
+
toggle,
|
|
47
|
+
togglePage,
|
|
48
|
+
pageSelectionState,
|
|
49
|
+
clear,
|
|
50
|
+
};
|
|
51
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @calliopelabs/ui: the Calliope AI operator UI, shared by Astrolift AI and
|
|
3
|
+
* Zentinelle AI. Styles: `@import "@calliopelabs/ui/preset.css"` after Tailwind.
|
|
4
|
+
*/
|
|
5
|
+
export { UIProvider, useUI, type LinkComponent, type LinkProps, type UIProviderProps, } from "./provider/UIProvider.js";
|
|
6
|
+
export { defaultMessages, mergeMessages, type MessageOverrides, type Messages, } from "./provider/messages.js";
|
|
7
|
+
export { cn } from "./lib/cn.js";
|
|
8
|
+
export { ACCENTS, ACCENT_KEYS, DENSITIES, DENSITY_KEYS, GROUNDS, GROUND_KEYS, MODE_TOKENS, SURFACES, type AccentSpec, type Density, type FamilyAccent, type Ground, type Mode, type StatusName, } from "./theme/tokens.js";
|
|
9
|
+
export { MIN_NON_TEXT_CONTRAST, MIN_TEXT_CONTRAST, contrastRatio, luminance, } from "./theme/contrast.js";
|
|
10
|
+
export { CORNERS, defineProduct, type BrandAccent, type Corners, type Product, type ProductAccent, type ProductConfig, type ProductTheme, } from "./appearance/product.js";
|
|
11
|
+
export { astroliftBrand, chatStudioBrand, cykickBrand, loadrBrand, workbenchBrand, zentinelleBrand, } from "./appearance/brands.js";
|
|
12
|
+
export { accentContrast, appearanceFromServer, applyAppearance, clearAppearance, inkFor, isCustomAccent, modeFor, normalize, normalizePartial, parseCustomAccent, readAppearance, resolveAppearance, writeAppearance, type Appearance, type CustomAccent, type OrgAppearancePolicy, type ResolvedAppearance, } from "./appearance/appearance.js";
|
|
13
|
+
export { appearanceScript, productStyles } from "./appearance/boot.js";
|
|
14
|
+
export { AppearanceScript, type AppearanceScriptProps } from "./appearance/AppearanceScript.js";
|
|
15
|
+
export { AppearanceProvider, useAppearance, useDensity, useProduct, type AppearanceContextValue, type AppearanceProviderProps, } from "./appearance/AppearanceProvider.js";
|
|
16
|
+
export { AppearancePicker } from "./appearance/AppearancePicker.js";
|
|
17
|
+
export { activeFor, areaSwitcher, localizeNav, visibleNav, type CrumbSwitchOption, type NavActive, type NavArea, type NavFunction, type NavGroup, type NavIcon, type NavModel, } from "./nav/model.js";
|
|
18
|
+
export { Nav, type NavProps } from "./nav/Nav.js";
|
|
19
|
+
export { AppShell, type AppShellProps } from "./shell/AppShell.js";
|
|
20
|
+
export { Sidebar, type SidebarProps } from "./shell/Sidebar.js";
|
|
21
|
+
export { BrandMark, type BrandMarkProps } from "./shell/BrandMark.js";
|
|
22
|
+
export { UserMenu, type UserMenuItem, type UserMenuProps } from "./shell/UserMenu.js";
|
|
23
|
+
export { RailPanel, type RailPanelProps } from "./shell/RailPanel.js";
|
|
24
|
+
export { RailTree, type RailTreeGroup, type RailTreeItem, type RailTreeProps, } from "./shell/RailTree.js";
|
|
25
|
+
export { useRailState, useRailWidth } from "./shell/rail-state.js";
|
|
26
|
+
export { PageFrame, type PageFrameProps } from "./page/PageFrame.js";
|
|
27
|
+
export { PageHeader, type PageHeaderProps } from "./page/PageHeader.js";
|
|
28
|
+
export { Breadcrumbs, type BreadcrumbsProps, type Crumb } from "./page/Breadcrumbs.js";
|
|
29
|
+
export { TabNav, type TabNavItem, type TabNavProps } from "./page/TabNav.js";
|
|
30
|
+
export { Section, type SectionProps } from "./page/Section.js";
|
|
31
|
+
export { DataTable, type DataTableProps } from "./data/DataTable.js";
|
|
32
|
+
export { List, ListItem, type ListItemProps, type ListProps } from "./data/List.js";
|
|
33
|
+
export { useRowSelection, type RowSelection } from "./data/use-row-selection.js";
|
|
34
|
+
export { DEFAULT_PAGE_SIZE, PAGE_SIZES, type Column, type EmptyStateSpec, type PageState, type SortDirection, type SortState, type TableController, } from "./data/types.js";
|
|
35
|
+
export { StatusBadge, type StatusBadgeProps } from "./status/StatusBadge.js";
|
|
36
|
+
export { StatusDot, type StatusDotProps } from "./status/StatusDot.js";
|
|
37
|
+
export { DOT_TONE, IN_FLIGHT, PILL_TONE, STATUS_TONES, TONE_TOKEN, type StatusTone, } from "./status/tones.js";
|
|
38
|
+
export { EmptyState, type EmptyStateProps } from "./states/EmptyState.js";
|
|
39
|
+
export { ErrorState, type ErrorStateProps } from "./states/ErrorState.js";
|
|
40
|
+
export { LoadingState, type LoadingStateProps } from "./states/LoadingState.js";
|
|
41
|
+
export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "./overlays/Dialog.js";
|
|
42
|
+
export { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger, } from "./overlays/Drawer.js";
|
|
43
|
+
export { ConfirmDialog, type ConfirmDialogProps, type ConfirmReason, } from "./overlays/ConfirmDialog.js";
|
|
44
|
+
export { Button, buttonVariants, type ButtonProps } from "./primitives/Button.js";
|
|
45
|
+
export { Input } from "./primitives/Input.js";
|
|
46
|
+
export { Checkbox } from "./primitives/Checkbox.js";
|
|
47
|
+
export { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "./primitives/DropdownMenu.js";
|
|
48
|
+
export { Popover, PopoverContent, PopoverTrigger } from "./primitives/Popover.js";
|
|
49
|
+
export { Tabs, TabsContent, TabsList, TabsTrigger } from "./primitives/Tabs.js";
|
|
50
|
+
export { Skeleton } from "./primitives/Skeleton.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @calliopelabs/ui: the Calliope AI operator UI, shared by Astrolift AI and
|
|
3
|
+
* Zentinelle AI. Styles: `@import "@calliopelabs/ui/preset.css"` after Tailwind.
|
|
4
|
+
*/
|
|
5
|
+
// Environment
|
|
6
|
+
export { UIProvider, useUI, } from "./provider/UIProvider.js";
|
|
7
|
+
export { defaultMessages, mergeMessages, } from "./provider/messages.js";
|
|
8
|
+
export { cn } from "./lib/cn.js";
|
|
9
|
+
// Tokens
|
|
10
|
+
export { ACCENTS, ACCENT_KEYS, DENSITIES, DENSITY_KEYS, GROUNDS, GROUND_KEYS, MODE_TOKENS, SURFACES, } from "./theme/tokens.js";
|
|
11
|
+
export { MIN_NON_TEXT_CONTRAST, MIN_TEXT_CONTRAST, contrastRatio, luminance, } from "./theme/contrast.js";
|
|
12
|
+
// Product and appearance
|
|
13
|
+
export { CORNERS, defineProduct, } from "./appearance/product.js";
|
|
14
|
+
export { astroliftBrand, chatStudioBrand, cykickBrand, loadrBrand, workbenchBrand, zentinelleBrand, } from "./appearance/brands.js";
|
|
15
|
+
export { accentContrast, appearanceFromServer, applyAppearance, clearAppearance, inkFor, isCustomAccent, modeFor, normalize, normalizePartial, parseCustomAccent, readAppearance, resolveAppearance, writeAppearance, } from "./appearance/appearance.js";
|
|
16
|
+
export { appearanceScript, productStyles } from "./appearance/boot.js";
|
|
17
|
+
export { AppearanceScript } from "./appearance/AppearanceScript.js";
|
|
18
|
+
export { AppearanceProvider, useAppearance, useDensity, useProduct, } from "./appearance/AppearanceProvider.js";
|
|
19
|
+
export { AppearancePicker } from "./appearance/AppearancePicker.js";
|
|
20
|
+
// Navigation
|
|
21
|
+
export { activeFor, areaSwitcher, localizeNav, visibleNav, } from "./nav/model.js";
|
|
22
|
+
export { Nav } from "./nav/Nav.js";
|
|
23
|
+
// Shell
|
|
24
|
+
export { AppShell } from "./shell/AppShell.js";
|
|
25
|
+
export { Sidebar } from "./shell/Sidebar.js";
|
|
26
|
+
export { BrandMark } from "./shell/BrandMark.js";
|
|
27
|
+
export { UserMenu } from "./shell/UserMenu.js";
|
|
28
|
+
export { RailPanel } from "./shell/RailPanel.js";
|
|
29
|
+
export { RailTree, } from "./shell/RailTree.js";
|
|
30
|
+
export { useRailState, useRailWidth } from "./shell/rail-state.js";
|
|
31
|
+
// Page
|
|
32
|
+
export { PageFrame } from "./page/PageFrame.js";
|
|
33
|
+
export { PageHeader } from "./page/PageHeader.js";
|
|
34
|
+
export { Breadcrumbs } from "./page/Breadcrumbs.js";
|
|
35
|
+
export { TabNav } from "./page/TabNav.js";
|
|
36
|
+
export { Section } from "./page/Section.js";
|
|
37
|
+
// Data
|
|
38
|
+
export { DataTable } from "./data/DataTable.js";
|
|
39
|
+
export { List, ListItem } from "./data/List.js";
|
|
40
|
+
export { useRowSelection } from "./data/use-row-selection.js";
|
|
41
|
+
export { DEFAULT_PAGE_SIZE, PAGE_SIZES, } from "./data/types.js";
|
|
42
|
+
// Status
|
|
43
|
+
export { StatusBadge } from "./status/StatusBadge.js";
|
|
44
|
+
export { StatusDot } from "./status/StatusDot.js";
|
|
45
|
+
export { DOT_TONE, IN_FLIGHT, PILL_TONE, STATUS_TONES, TONE_TOKEN, } from "./status/tones.js";
|
|
46
|
+
// States
|
|
47
|
+
export { EmptyState } from "./states/EmptyState.js";
|
|
48
|
+
export { ErrorState } from "./states/ErrorState.js";
|
|
49
|
+
export { LoadingState } from "./states/LoadingState.js";
|
|
50
|
+
// Overlays
|
|
51
|
+
export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "./overlays/Dialog.js";
|
|
52
|
+
export { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger, } from "./overlays/Drawer.js";
|
|
53
|
+
export { ConfirmDialog, } from "./overlays/ConfirmDialog.js";
|
|
54
|
+
// Primitives
|
|
55
|
+
export { Button, buttonVariants } from "./primitives/Button.js";
|
|
56
|
+
export { Input } from "./primitives/Input.js";
|
|
57
|
+
export { Checkbox } from "./primitives/Checkbox.js";
|
|
58
|
+
export { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "./primitives/DropdownMenu.js";
|
|
59
|
+
export { Popover, PopoverContent, PopoverTrigger } from "./primitives/Popover.js";
|
|
60
|
+
export { Tabs, TabsContent, TabsList, TabsTrigger } from "./primitives/Tabs.js";
|
|
61
|
+
export { Skeleton } from "./primitives/Skeleton.js";
|
package/dist/lib/cn.d.ts
ADDED
package/dist/lib/cn.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* localStorage as a React external store, for per-person display
|
|
3
|
+
* preferences (appearance, rail state). Reads and writes never throw: Safari
|
|
4
|
+
* private mode and embedded webviews throw on storage access. A write that
|
|
5
|
+
* storage refuses is held for the rest of the page, so the choice still
|
|
6
|
+
* applies until reload.
|
|
7
|
+
*/
|
|
8
|
+
/** Dispatched on window after every write in this tab; other tabs hear `storage`. */
|
|
9
|
+
export declare const LOCAL_STORE_EVENT = "calliope-ui:storage";
|
|
10
|
+
export declare function readLocal(key: string): string | null;
|
|
11
|
+
export declare function writeLocal(key: string, value: string | null): void;
|
|
12
|
+
export declare function subscribeLocal(onChange: () => void): () => void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* localStorage as a React external store, for per-person display
|
|
3
|
+
* preferences (appearance, rail state). Reads and writes never throw: Safari
|
|
4
|
+
* private mode and embedded webviews throw on storage access. A write that
|
|
5
|
+
* storage refuses is held for the rest of the page, so the choice still
|
|
6
|
+
* applies until reload.
|
|
7
|
+
*/
|
|
8
|
+
/** Dispatched on window after every write in this tab; other tabs hear `storage`. */
|
|
9
|
+
export const LOCAL_STORE_EVENT = "calliope-ui:storage";
|
|
10
|
+
const unsaved = new Map();
|
|
11
|
+
function storage() {
|
|
12
|
+
try {
|
|
13
|
+
return typeof window === "undefined" ? null : window.localStorage;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function readLocal(key) {
|
|
20
|
+
if (unsaved.has(key))
|
|
21
|
+
return unsaved.get(key) ?? null;
|
|
22
|
+
try {
|
|
23
|
+
return storage()?.getItem(key) ?? null;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function writeLocal(key, value) {
|
|
30
|
+
try {
|
|
31
|
+
const s = storage();
|
|
32
|
+
if (!s)
|
|
33
|
+
throw new Error("storage unavailable");
|
|
34
|
+
if (value === null)
|
|
35
|
+
s.removeItem(key);
|
|
36
|
+
else
|
|
37
|
+
s.setItem(key, value);
|
|
38
|
+
unsaved.delete(key);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
unsaved.set(key, value);
|
|
42
|
+
}
|
|
43
|
+
if (typeof window !== "undefined") {
|
|
44
|
+
window.dispatchEvent(new CustomEvent(LOCAL_STORE_EVENT, { detail: key }));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function subscribeLocal(onChange) {
|
|
48
|
+
window.addEventListener("storage", onChange);
|
|
49
|
+
window.addEventListener(LOCAL_STORE_EVENT, onChange);
|
|
50
|
+
return () => {
|
|
51
|
+
window.removeEventListener("storage", onChange);
|
|
52
|
+
window.removeEventListener(LOCAL_STORE_EVENT, onChange);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { NavActive, NavModel } from "./model.js";
|
|
3
|
+
export interface NavProps {
|
|
4
|
+
/** The model, already filtered to what the viewer may open (`visibleNav`). */
|
|
5
|
+
nav: NavModel;
|
|
6
|
+
/** The row the current page belongs to (`activeFor`). */
|
|
7
|
+
active: NavActive | null;
|
|
8
|
+
/** Icons only, with each area's functions in a flyout. */
|
|
9
|
+
collapsed?: boolean;
|
|
10
|
+
/** Called when a row is followed, so a drawer holding the nav can close. */
|
|
11
|
+
onNavigate?: () => void;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The areas and their functions (Astrolift spec 44 §4.1). Expanded, each area
|
|
16
|
+
* is a heading that folds, over its groups and rows; a flat area (Home,
|
|
17
|
+
* Overview) is just its rows. Collapsed, each area is an icon, and hovering,
|
|
18
|
+
* focusing or pressing it opens its rows as a flyout. The current row is the
|
|
19
|
+
* one lit row: an accent tint and a 2px accent edge.
|
|
20
|
+
*/
|
|
21
|
+
export declare function Nav({ nav, active, collapsed, onNavigate, className }: NavProps): React.JSX.Element;
|
package/dist/nav/Nav.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { ChevronDownIcon } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../primitives/Popover.js";
|
|
7
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
8
|
+
/**
|
|
9
|
+
* The areas and their functions (Astrolift spec 44 §4.1). Expanded, each area
|
|
10
|
+
* is a heading that folds, over its groups and rows; a flat area (Home,
|
|
11
|
+
* Overview) is just its rows. Collapsed, each area is an icon, and hovering,
|
|
12
|
+
* focusing or pressing it opens its rows as a flyout. The current row is the
|
|
13
|
+
* one lit row: an accent tint and a 2px accent edge.
|
|
14
|
+
*/
|
|
15
|
+
export function Nav({ nav, active, collapsed = false, onNavigate, className }) {
|
|
16
|
+
return (_jsx("div", { className: cn("flex flex-col", collapsed ? "items-center gap-1" : "gap-2", className), children: nav.map((area) => collapsed ? (_jsx(CollapsedArea, { area: area, active: active, onNavigate: onNavigate }, area.key)) : (_jsx(ExpandedArea, { area: area, active: active, onNavigate: onNavigate }, area.key))) }));
|
|
17
|
+
}
|
|
18
|
+
function FunctionLink({ fn, isActive, onNavigate, }) {
|
|
19
|
+
const { Link } = useUI();
|
|
20
|
+
const Icon = fn.icon;
|
|
21
|
+
return (_jsxs(Link, { href: fn.href, onClick: onNavigate, "aria-current": isActive ? "page" : undefined, ...fn.attributes, className: cn("flex h-8 min-w-0 items-center gap-2 rounded-lg px-2 text-sm focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", isActive
|
|
22
|
+
? "bg-primary/15 font-medium text-sidebar-foreground shadow-[inset_2px_0_0_var(--primary)]"
|
|
23
|
+
: "text-sidebar-foreground/80 hover:bg-sidebar-accent/60 hover:text-sidebar-foreground"), children: [Icon && _jsx(Icon, { className: "size-4 shrink-0 opacity-80", "aria-hidden": true }), _jsx("span", { className: "truncate", children: fn.label })] }));
|
|
24
|
+
}
|
|
25
|
+
function Rows({ area, active, onNavigate, }) {
|
|
26
|
+
return (_jsx(_Fragment, { children: area.groups.map((group, i) => (_jsxs("div", { className: "flex flex-col gap-0.5", children: [group.label && (_jsx("div", { className: "px-2 pt-2 pb-1 text-2xs text-muted-foreground", children: group.label })), group.functions.map((fn) => (_jsx(FunctionLink, { fn: fn, isActive: active?.area === area.key && active.fn === fn.key, onNavigate: onNavigate }, fn.key)))] }, group.key ?? group.label ?? i))) }));
|
|
27
|
+
}
|
|
28
|
+
function ExpandedArea({ area, active, onNavigate, }) {
|
|
29
|
+
const [open, setOpen] = React.useState(true);
|
|
30
|
+
const regionId = React.useId();
|
|
31
|
+
if (area.flat) {
|
|
32
|
+
return (_jsx("div", { className: "flex flex-col gap-0.5", children: _jsx(Rows, { area: area, active: active, onNavigate: onNavigate }) }));
|
|
33
|
+
}
|
|
34
|
+
return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("button", { type: "button", onClick: () => setOpen((o) => !o), "aria-expanded": open, "aria-controls": regionId, className: "flex h-7 w-full items-center justify-between rounded-lg px-2 text-2xs font-semibold tracking-wider text-muted-foreground uppercase hover:text-sidebar-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", children: [area.label, _jsx(ChevronDownIcon, { className: cn("size-3 transition-transform", !open && "-rotate-90"), "aria-hidden": true })] }), _jsx("div", { id: regionId, hidden: !open, className: "flex flex-col gap-0.5", children: _jsx(Rows, { area: area, active: active, onNavigate: onNavigate }) })] }));
|
|
35
|
+
}
|
|
36
|
+
/** How long the pointer may be away from a flyout before it closes. */
|
|
37
|
+
const FLYOUT_GRACE_MS = 150;
|
|
38
|
+
function CollapsedArea({ area, active, onNavigate, }) {
|
|
39
|
+
const { Link } = useUI();
|
|
40
|
+
const [open, setOpen] = React.useState(false);
|
|
41
|
+
const pointerOpened = React.useRef(false);
|
|
42
|
+
const closeTimer = React.useRef(null);
|
|
43
|
+
const isActive = active?.area === area.key;
|
|
44
|
+
const Icon = area.icon;
|
|
45
|
+
const only = area.groups.length === 1 && area.groups[0]?.functions.length === 1
|
|
46
|
+
? area.groups[0].functions[0]
|
|
47
|
+
: null;
|
|
48
|
+
React.useEffect(() => {
|
|
49
|
+
const timer = closeTimer;
|
|
50
|
+
return () => {
|
|
51
|
+
if (timer.current)
|
|
52
|
+
clearTimeout(timer.current);
|
|
53
|
+
};
|
|
54
|
+
}, []);
|
|
55
|
+
const tile = (_jsx("span", { className: cn("flex size-8 items-center justify-center rounded-lg", isActive
|
|
56
|
+
? "bg-primary/15 text-sidebar-foreground shadow-[inset_2px_0_0_var(--primary)]"
|
|
57
|
+
: "text-sidebar-foreground/80 hover:bg-sidebar-accent/60"), children: _jsx(Icon, { className: "size-4", "aria-hidden": true }) }));
|
|
58
|
+
// One row: the icon is that row's link, no flyout needed.
|
|
59
|
+
if (only) {
|
|
60
|
+
return (_jsx(Link, { href: only.href, onClick: onNavigate, "aria-label": area.flat ? only.label : area.label, title: area.flat ? only.label : area.label, "aria-current": isActive ? "page" : undefined, ...only.attributes, className: "flex rounded-lg focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", children: tile }));
|
|
61
|
+
}
|
|
62
|
+
const cancelClose = () => {
|
|
63
|
+
if (closeTimer.current)
|
|
64
|
+
clearTimeout(closeTimer.current);
|
|
65
|
+
closeTimer.current = null;
|
|
66
|
+
};
|
|
67
|
+
const scheduleClose = () => {
|
|
68
|
+
cancelClose();
|
|
69
|
+
closeTimer.current = setTimeout(() => setOpen(false), FLYOUT_GRACE_MS);
|
|
70
|
+
};
|
|
71
|
+
return (_jsxs(Popover, { open: open, onOpenChange: (next) => {
|
|
72
|
+
if (!next)
|
|
73
|
+
pointerOpened.current = false;
|
|
74
|
+
setOpen(next);
|
|
75
|
+
}, children: [_jsx(PopoverTrigger, { "aria-label": area.label, title: area.label, className: "flex rounded-lg focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", onPointerEnter: (event) => {
|
|
76
|
+
if (event.pointerType !== "mouse")
|
|
77
|
+
return;
|
|
78
|
+
cancelClose();
|
|
79
|
+
pointerOpened.current = true;
|
|
80
|
+
setOpen(true);
|
|
81
|
+
}, onPointerLeave: (event) => {
|
|
82
|
+
if (event.pointerType === "mouse")
|
|
83
|
+
scheduleClose();
|
|
84
|
+
}, children: tile }), _jsxs(PopoverContent, { side: "right", align: "start", className: "w-56 gap-0 p-2",
|
|
85
|
+
// Opened by hovering: leave focus where the person is. Opened from the
|
|
86
|
+
// keyboard: move it in, to the first row.
|
|
87
|
+
onOpenAutoFocus: (event) => {
|
|
88
|
+
if (pointerOpened.current)
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
}, onPointerEnter: cancelClose, onPointerLeave: (event) => {
|
|
91
|
+
if (event.pointerType === "mouse")
|
|
92
|
+
scheduleClose();
|
|
93
|
+
}, children: [_jsx("div", { className: "px-2 pb-1 text-2xs font-semibold tracking-wider text-muted-foreground uppercase", children: area.label }), _jsx(Rows, { area: area, active: active, onNavigate: () => {
|
|
94
|
+
setOpen(false);
|
|
95
|
+
onNavigate?.();
|
|
96
|
+
} })] })] }));
|
|
97
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* The navigation model (Astrolift spec 44 §4.1 and §4.4; Zentinelle
|
|
4
|
+
* ui-overhaul-ia §4.1): areas, optional groups inside an area, and the
|
|
5
|
+
* functions in each group. A function is one sidebar row; its tabs, views
|
|
6
|
+
* and actions live inside its pages. Pure data plus pure functions, so the
|
|
7
|
+
* sidebar, the breadcrumb switcher and the tests read the same model, and a
|
|
8
|
+
* Server Component can compute the active row.
|
|
9
|
+
*/
|
|
10
|
+
/** Any icon component: lucide-react's, or an app's own SVG. */
|
|
11
|
+
export type NavIcon = React.ComponentType<{
|
|
12
|
+
className?: string;
|
|
13
|
+
"aria-hidden"?: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
export interface NavFunction {
|
|
16
|
+
key: string;
|
|
17
|
+
label: string;
|
|
18
|
+
href: string;
|
|
19
|
+
icon?: NavIcon;
|
|
20
|
+
/**
|
|
21
|
+
* Capabilities that show this row; any one of them is enough. Absent means
|
|
22
|
+
* always shown. The app decides what a capability is: Astrolift passes
|
|
23
|
+
* module keys (`me.modules.<key>.canView`), Zentinelle passes roles.
|
|
24
|
+
*/
|
|
25
|
+
requires?: string | readonly string[];
|
|
26
|
+
/** Other path prefixes this row owns, for pages that sit under several routes. */
|
|
27
|
+
also?: readonly string[];
|
|
28
|
+
/** Extra attributes on the row's link, such as a tour anchor (`data-tour`). */
|
|
29
|
+
attributes?: Readonly<Record<`data-${string}`, string>>;
|
|
30
|
+
}
|
|
31
|
+
export interface NavGroup {
|
|
32
|
+
key?: string;
|
|
33
|
+
label?: string;
|
|
34
|
+
functions: readonly NavFunction[];
|
|
35
|
+
}
|
|
36
|
+
export interface NavArea {
|
|
37
|
+
key: string;
|
|
38
|
+
label: string;
|
|
39
|
+
icon: NavIcon;
|
|
40
|
+
/** Render the area's functions as top-level rows, with no area heading (Home, Overview). */
|
|
41
|
+
flat?: boolean;
|
|
42
|
+
groups: readonly NavGroup[];
|
|
43
|
+
}
|
|
44
|
+
export type NavModel = readonly NavArea[];
|
|
45
|
+
/** The row the current page belongs to. */
|
|
46
|
+
export interface NavActive {
|
|
47
|
+
area: string;
|
|
48
|
+
fn: string;
|
|
49
|
+
}
|
|
50
|
+
/** The model with every row the viewer may not open removed, and empty groups and areas dropped. */
|
|
51
|
+
export declare function visibleNav(nav: NavModel, can: (capability: string) => boolean): NavArea[];
|
|
52
|
+
/**
|
|
53
|
+
* Which area and function own `pathname`: the function whose href (or `also`
|
|
54
|
+
* prefix) is the longest prefix of it, so /agents/skills/42 is Skills, not
|
|
55
|
+
* Agents. Query strings and fragments are ignored.
|
|
56
|
+
*/
|
|
57
|
+
export declare function activeFor(nav: NavModel, pathname: string): NavActive | null;
|
|
58
|
+
export interface CrumbSwitchOption {
|
|
59
|
+
label: string;
|
|
60
|
+
href: string;
|
|
61
|
+
active?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The first crumb of a page inside an area: the area's name, switching
|
|
65
|
+
* between its functions with the current one checked. Read from the model,
|
|
66
|
+
* so the sidebar and the breadcrumb always agree.
|
|
67
|
+
*/
|
|
68
|
+
export declare function areaSwitcher(nav: NavModel, area: string, activeFn: string): {
|
|
69
|
+
label: string;
|
|
70
|
+
switcher: CrumbSwitchOption[];
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The model with every label passed through `translate`, for apps whose
|
|
74
|
+
* copy lives in a message catalog. `fallback` is the model's own label;
|
|
75
|
+
* return it when the catalog has no entry.
|
|
76
|
+
*/
|
|
77
|
+
export declare function localizeNav(nav: NavModel, translate: (kind: "area" | "group" | "function", key: string, fallback: string) => string): NavArea[];
|