@parallelworks/ui 0.84.0 → 0.84.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Loader.d.ts +6 -4
- package/dist/components/Loader.js +53 -1
- package/dist/components/RefreshButton.d.ts +3 -2
- package/dist/components/RefreshButton.js +25 -14
- package/dist/components/Table.d.ts +14 -1
- package/dist/components/Table.js +18 -4
- package/dist/components/Tabs.js +1 -1
- package/dist/components/stickyTop.d.ts +9 -0
- package/dist/components/stickyTop.js +10 -1
- package/dist/graph/DependencyGraph.js +1 -1
- package/dist/list/ActionToolbar.js +1 -1
- package/dist/list/ListPage.d.ts +24 -0
- package/dist/list/ListPage.js +105 -17
- package/dist/list/ListTable.d.ts +2 -6
- package/dist/list/ListTable.js +24 -28
- package/dist/list/index.d.ts +2 -2
- package/dist/list/index.js +2 -2
- package/dist/list/listView.d.ts +6 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -14,10 +14,12 @@ export interface LoaderProps {
|
|
|
14
14
|
* label: hidden from assistive technology, and in the text's color. */
|
|
15
15
|
decorative?: boolean;
|
|
16
16
|
/** Centers it in the room it stands for, so the page needs no box of its
|
|
17
|
-
* own: `screen` the whole window, such as while the app starts; `
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
17
|
+
* own: `screen` the whole window, such as while the app starts; `content`
|
|
18
|
+
* the app's content area beside its sidebar and under its top bar, however
|
|
19
|
+
* the page around it is laid out; `page` the page's scrolling region, such
|
|
20
|
+
* as while a route loads; `section` a pane or a card with no height of its
|
|
21
|
+
* own. */
|
|
22
|
+
fill?: 'screen' | 'content' | 'page' | 'section' | undefined;
|
|
21
23
|
}
|
|
22
24
|
/** Something on the way that has no shape to sketch yet. When its shape is
|
|
23
25
|
* known, such as rows of a list, sketch it with `Skeleton` instead. */
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
2
|
import { RunningIcon } from "../icons.js";
|
|
3
|
+
import { pinnedAbove, scrollContainer } from "./stickyTop.js";
|
|
3
4
|
import { useStrings } from "./Provider.js";
|
|
4
|
-
import {
|
|
5
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
6
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
7
|
//#region src/components/Loader.tsx
|
|
6
8
|
var RIPPLE_STAGGER_S = .22;
|
|
7
9
|
var LOGO_RINGS = [
|
|
@@ -19,6 +21,10 @@ var fillClasses = {
|
|
|
19
21
|
* known, such as rows of a list, sketch it with `Skeleton` instead. */
|
|
20
22
|
function Loader({ fill, ...props }) {
|
|
21
23
|
if (!fill) return /* @__PURE__ */ jsx(LoaderMark, { ...props });
|
|
24
|
+
if (fill === "content") return /* @__PURE__ */ jsx(ContentArea, { children: /* @__PURE__ */ jsx(LoaderMark, {
|
|
25
|
+
...props,
|
|
26
|
+
size: props.size ?? 48
|
|
27
|
+
}) });
|
|
22
28
|
return /* @__PURE__ */ jsx("div", {
|
|
23
29
|
className: fillClasses[fill],
|
|
24
30
|
children: /* @__PURE__ */ jsx(LoaderMark, {
|
|
@@ -68,5 +74,51 @@ function LoaderMark({ size = 48, label, variant = "spinner", inline = false, col
|
|
|
68
74
|
})]
|
|
69
75
|
});
|
|
70
76
|
}
|
|
77
|
+
/** Centers its content in the box of the scrolling region it sits in, which in an app with a
|
|
78
|
+
* sidebar is its content area, below what that region pins at its top and held to what of it
|
|
79
|
+
* the window shows; the window, where the document itself scrolls. It lays nothing out around
|
|
80
|
+
* it and takes no pointer. */
|
|
81
|
+
function ContentArea({ children }) {
|
|
82
|
+
const anchor = useRef(null);
|
|
83
|
+
const [box, setBox] = useState(null);
|
|
84
|
+
useLayoutEffect(() => {
|
|
85
|
+
const el = anchor.current;
|
|
86
|
+
if (!el) return;
|
|
87
|
+
const area = scrollContainer(el);
|
|
88
|
+
if (area === el.ownerDocument.documentElement) return;
|
|
89
|
+
const view = el.ownerDocument.defaultView ?? window;
|
|
90
|
+
const pinned = pinnedAbove(el);
|
|
91
|
+
const place = () => {
|
|
92
|
+
const rect = area.getBoundingClientRect();
|
|
93
|
+
const chrome = Math.max(0, ...pinned.chrome.map((c) => c.getBoundingClientRect().bottom));
|
|
94
|
+
const top = Math.max(rect.top, chrome, 0);
|
|
95
|
+
const bottom = Math.min(rect.bottom, view.innerHeight);
|
|
96
|
+
setBox({
|
|
97
|
+
top,
|
|
98
|
+
left: rect.left,
|
|
99
|
+
width: rect.width,
|
|
100
|
+
height: Math.max(bottom - top, 0)
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
place();
|
|
104
|
+
view.addEventListener("resize", place);
|
|
105
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(place);
|
|
106
|
+
observer?.observe(area);
|
|
107
|
+
for (const chrome of pinned.chrome) observer?.observe(chrome);
|
|
108
|
+
return () => {
|
|
109
|
+
view.removeEventListener("resize", place);
|
|
110
|
+
observer?.disconnect();
|
|
111
|
+
};
|
|
112
|
+
}, []);
|
|
113
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
114
|
+
ref: anchor,
|
|
115
|
+
hidden: true
|
|
116
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
117
|
+
"data-loader-area": "",
|
|
118
|
+
className: "pointer-events-none fixed inset-0 flex items-center justify-center",
|
|
119
|
+
style: box ?? void 0,
|
|
120
|
+
children
|
|
121
|
+
})] });
|
|
122
|
+
}
|
|
71
123
|
//#endregion
|
|
72
124
|
export { Loader };
|
|
@@ -14,8 +14,9 @@ export interface RefreshButtonProps {
|
|
|
14
14
|
/** A button's look, `ghost` by default; `inline` is text-sized muted text with no box, for
|
|
15
15
|
* a line of details such as a header's "Updated 2 minutes ago". */
|
|
16
16
|
variant?: ButtonVariant | 'inline' | undefined;
|
|
17
|
-
/** Only the icon shows, with "Refresh" as its name and tooltip.
|
|
18
|
-
|
|
17
|
+
/** Only the icon shows, with "Refresh" as its name and tooltip. `phone` shows the label on a
|
|
18
|
+
* wider screen and only the icon on a phone, the label still its name for a screen reader. */
|
|
19
|
+
iconOnly?: boolean | 'phone' | undefined;
|
|
19
20
|
className?: string | undefined;
|
|
20
21
|
}
|
|
21
22
|
/** Fetches again on demand, and says so while it does, out loud too. */
|
|
@@ -7,6 +7,12 @@ import { useRelativeTime } from "./relativeTime.js";
|
|
|
7
7
|
import { useState } from "react";
|
|
8
8
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
//#region src/components/RefreshButton.tsx
|
|
10
|
+
var phoneSquares = {
|
|
11
|
+
xs: "max-sm:w-6 max-sm:px-0",
|
|
12
|
+
sm: "max-sm:w-7 max-sm:px-0",
|
|
13
|
+
md: "max-sm:w-8 max-sm:px-0",
|
|
14
|
+
lg: "max-sm:w-10 max-sm:px-0"
|
|
15
|
+
};
|
|
10
16
|
var inlineClasses = "inline-flex cursor-pointer items-center gap-1 rounded px-1 py-0.5 text-(--theme-muted-text-color) transition-colors hover:bg-(--theme-muted-panel-bg) hover:text-(--theme-app) focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--theme-focus) [&_svg]:size-3";
|
|
11
17
|
/** Fetches again on demand, and says so while it does, out loud too. */
|
|
12
18
|
function RefreshButton({ onRefresh, refreshing = false, updatedAt, size = "md", variant = "ghost", iconOnly = false, className }) {
|
|
@@ -14,6 +20,8 @@ function RefreshButton({ onRefresh, refreshing = false, updatedAt, size = "md",
|
|
|
14
20
|
const [pending, setPending] = useState(false);
|
|
15
21
|
const busy = refreshing || pending;
|
|
16
22
|
const ago = useRelativeTime(updatedAt);
|
|
23
|
+
const phone = iconOnly === "phone";
|
|
24
|
+
const square = iconOnly === true;
|
|
17
25
|
const refresh = () => {
|
|
18
26
|
if (busy) return;
|
|
19
27
|
const result = onRefresh();
|
|
@@ -27,27 +35,30 @@ function RefreshButton({ onRefresh, refreshing = false, updatedAt, size = "md",
|
|
|
27
35
|
type: "button",
|
|
28
36
|
onClick: refresh,
|
|
29
37
|
"aria-busy": busy,
|
|
30
|
-
"aria-label":
|
|
38
|
+
"aria-label": square ? t.refresh : void 0,
|
|
31
39
|
className: cx(variant === "inline" ? inlineClasses : buttonClasses({
|
|
32
40
|
variant,
|
|
33
41
|
size,
|
|
34
|
-
iconOnly
|
|
35
|
-
}), "group aria-busy:cursor-progress", className),
|
|
42
|
+
iconOnly: square
|
|
43
|
+
}), phone && variant !== "inline" && phoneSquares[size], "group aria-busy:cursor-progress", className),
|
|
36
44
|
children: [/* @__PURE__ */ jsx(RefreshIcon, {
|
|
37
45
|
"aria-hidden": "true",
|
|
38
46
|
className: cx("shrink-0 motion-reduce:animate-none motion-reduce:transition-none", busy ? "animate-spin" : "transition-transform duration-300 group-hover:rotate-180")
|
|
39
|
-
}), !
|
|
40
|
-
className: "sr-only",
|
|
41
|
-
children:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
children:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
}), !square && /* @__PURE__ */ jsx("span", {
|
|
48
|
+
className: phone ? "max-sm:sr-only" : void 0,
|
|
49
|
+
children: ago ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
|
|
50
|
+
className: "sr-only",
|
|
51
|
+
children: [t.refresh, ". "]
|
|
52
|
+
}), t.updated(ago)] }) : busy ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
53
|
+
className: "sr-only",
|
|
54
|
+
children: t.refresh
|
|
55
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
56
|
+
"aria-hidden": "true",
|
|
57
|
+
children: t.refreshing
|
|
58
|
+
})] }) : t.refresh
|
|
59
|
+
})]
|
|
49
60
|
});
|
|
50
|
-
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
61
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [square ? /* @__PURE__ */ jsx(Tooltip, {
|
|
51
62
|
content: t.refresh,
|
|
52
63
|
children: button
|
|
53
64
|
}) : button, /* @__PURE__ */ jsx("span", {
|
|
@@ -5,11 +5,24 @@ export type SortDirection = 'ascending' | 'descending' | 'none';
|
|
|
5
5
|
* height, such as a key and value list or a dashboard card's runs; `dense`
|
|
6
6
|
* sets small type in tight rows, for a panel of figures. */
|
|
7
7
|
export type TableDensity = 'comfortable' | 'compact' | 'dense';
|
|
8
|
+
/** What a list row asks of the cell in a place, through whatever component of the host's renders
|
|
9
|
+
* it: its column's classes, content to show after its own, or that it shows stacked, under the
|
|
10
|
+
* first cell's, as a line in place of a cell. */
|
|
11
|
+
export interface CellPlacement {
|
|
12
|
+
className?: string | undefined;
|
|
13
|
+
after?: React.ReactNode;
|
|
14
|
+
stacked?: boolean | undefined;
|
|
15
|
+
}
|
|
16
|
+
/** A stacked column's value on the line under a row's name: it flows inline with the others and
|
|
17
|
+
* wraps between them, never inside a value, which truncates if it is wider than the line. */
|
|
18
|
+
export declare const STACKED_ITEM = "flex min-w-0 max-w-full items-center whitespace-nowrap [&>*]:min-w-0 [&>*]:truncate";
|
|
19
|
+
/** Set by a list row around each of its cells; a `Table.Item` takes what it asks. */
|
|
20
|
+
export declare const CellPlacementContext: React.Context<CellPlacement | null>;
|
|
8
21
|
interface ItemProps extends React.TdHTMLAttributes<HTMLTableCellElement> {
|
|
9
22
|
/** Lets the text wrap; by default a cell keeps to one line. */
|
|
10
23
|
wrap?: boolean;
|
|
11
24
|
}
|
|
12
|
-
declare function Item({ wrap, children, className, ...tdProps }: ItemProps): React.JSX.Element;
|
|
25
|
+
declare function Item({ wrap, children, className, ...tdProps }: ItemProps): React.JSX.Element | null;
|
|
13
26
|
interface HeaderProps {
|
|
14
27
|
children?: React.ReactNode;
|
|
15
28
|
className?: string | undefined;
|
package/dist/components/Table.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
2
|
import { SortDownIcon, SortUnsortedIcon, SortUpIcon } from "../icons.js";
|
|
3
|
-
import { Loader } from "./Loader.js";
|
|
4
3
|
import { useStickyTop } from "./stickyTop.js";
|
|
4
|
+
import { Loader } from "./Loader.js";
|
|
5
5
|
import React, { createContext, useContext, useState } from "react";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
//#region src/components/Table.tsx
|
|
@@ -23,12 +23,26 @@ var headerY = {
|
|
|
23
23
|
dense: "py-1"
|
|
24
24
|
};
|
|
25
25
|
var cellX = "px-3";
|
|
26
|
+
/** A stacked column's value on the line under a row's name: it flows inline with the others and
|
|
27
|
+
* wraps between them, never inside a value, which truncates if it is wider than the line. */
|
|
28
|
+
var STACKED_ITEM = "flex min-w-0 max-w-full items-center whitespace-nowrap [&>*]:min-w-0 [&>*]:truncate";
|
|
29
|
+
/** Set by a list row around each of its cells; a `Table.Item` takes what it asks. */
|
|
30
|
+
var CellPlacementContext = createContext(null);
|
|
26
31
|
function Item({ wrap = false, children, className, ...tdProps }) {
|
|
27
32
|
const density = useContext(DensityContext);
|
|
33
|
+
const placed = useContext(CellPlacementContext);
|
|
34
|
+
if (placed?.stacked) return React.Children.toArray(children).length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
35
|
+
className: STACKED_ITEM,
|
|
36
|
+
"data-stacked": true,
|
|
37
|
+
children
|
|
38
|
+
}) : null;
|
|
28
39
|
return /* @__PURE__ */ jsx("td", {
|
|
29
40
|
...tdProps,
|
|
30
|
-
className: cx(cellX, cellY[density], wrap ? wrapY[density] : "whitespace-nowrap", className),
|
|
31
|
-
children
|
|
41
|
+
className: cx(cellX, cellY[density], wrap ? wrapY[density] : "overflow-clip text-ellipsis whitespace-nowrap [overflow-clip-margin:4px]", className, placed?.className),
|
|
42
|
+
children: /* @__PURE__ */ jsxs(CellPlacementContext, {
|
|
43
|
+
value: null,
|
|
44
|
+
children: [children, placed?.after]
|
|
45
|
+
})
|
|
32
46
|
});
|
|
33
47
|
}
|
|
34
48
|
var sortIcons = {
|
|
@@ -107,4 +121,4 @@ function Table({ density = "comfortable", columns, caps = true, flush = false, c
|
|
|
107
121
|
Table.Header = Header;
|
|
108
122
|
Table.Item = Item;
|
|
109
123
|
//#endregion
|
|
110
|
-
export { Table };
|
|
124
|
+
export { CellPlacementContext, STACKED_ITEM, Table };
|
package/dist/components/Tabs.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
|
+
import { useStickyTop } from "./stickyTop.js";
|
|
2
3
|
import { Tooltip } from "./Tooltip.js";
|
|
3
4
|
import { AppLink } from "./AppLink.js";
|
|
4
5
|
import { SegmentContent, segmentClassName, segmentGroupClassName } from "./segmented.js";
|
|
5
|
-
import { useStickyTop } from "./stickyTop.js";
|
|
6
6
|
import { useState } from "react";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
import { Tabs } from "@base-ui/react/tabs";
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/** Pins a bar to the top of its scroller, over what scrolls under it.
|
|
2
2
|
* `useStickyTop` moves it below whatever else is pinned there. */
|
|
3
3
|
export declare const stickyClasses = "sticky top-0 z-(--layer-sticky) bg-(--theme-app-bg)";
|
|
4
|
+
/** The box a sticky element sticks to: its nearest ancestor that scrolls, or
|
|
5
|
+
* the document's when none does. */
|
|
6
|
+
export declare function scrollContainer(el: Element): Element;
|
|
7
|
+
/** What a page pins at the top of `el`'s scroller over it, such as a page header and a toolbar:
|
|
8
|
+
* those elements, and how far below the scroller's top their bottom edge sits. */
|
|
9
|
+
export declare function pinnedAbove(el: Element): {
|
|
10
|
+
chrome: HTMLElement[];
|
|
11
|
+
bottom: number;
|
|
12
|
+
};
|
|
4
13
|
/**
|
|
5
14
|
* Pins a `sticky` element just below whatever its scroller already pins at
|
|
6
15
|
* the top, such as a page header, a toolbar and a tab row, rather than at
|
|
@@ -47,6 +47,15 @@ function pinnedBottom(chrome) {
|
|
|
47
47
|
const height = chrome.getBoundingClientRect().height;
|
|
48
48
|
return height > 0 ? (Number.parseFloat(getComputedStyle(chrome).top) || 0) + height : 0;
|
|
49
49
|
}
|
|
50
|
+
/** What a page pins at the top of `el`'s scroller over it, such as a page header and a toolbar:
|
|
51
|
+
* those elements, and how far below the scroller's top their bottom edge sits. */
|
|
52
|
+
function pinnedAbove(el) {
|
|
53
|
+
const { chrome } = chromeAbove(el, scrollContainer(el));
|
|
54
|
+
return {
|
|
55
|
+
chrome,
|
|
56
|
+
bottom: Math.max(0, ...chrome.map(pinnedBottom))
|
|
57
|
+
};
|
|
58
|
+
}
|
|
50
59
|
/**
|
|
51
60
|
* Pins a `sticky` element just below whatever its scroller already pins at
|
|
52
61
|
* the top, such as a page header, a toolbar and a tab row, rather than at
|
|
@@ -95,4 +104,4 @@ function useStickyTop(el, enabled) {
|
|
|
95
104
|
}, [el, enabled]);
|
|
96
105
|
}
|
|
97
106
|
//#endregion
|
|
98
|
-
export { stickyClasses, useStickyTop };
|
|
107
|
+
export { pinnedAbove, scrollContainer, stickyClasses, useStickyTop };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
2
|
import { ArrowLeftIcon, ArrowRightIcon, ChevronLeftIcon, ChevronRightIcon, CloseIcon, FileTextIcon, ZoomInIcon, ZoomOutIcon } from "../icons.js";
|
|
3
|
+
import { useStickyTop } from "../components/stickyTop.js";
|
|
3
4
|
import { Loader } from "../components/Loader.js";
|
|
4
5
|
import { useLocale, useRunFile, useSlots, useStrings, useWorkflowEngine } from "../components/Provider.js";
|
|
5
6
|
import { Button } from "../components/Button.js";
|
|
@@ -10,7 +11,6 @@ import { DisclosurePanel } from "../components/Disclosure.js";
|
|
|
10
11
|
import { formatDateTime } from "../format.js";
|
|
11
12
|
import { Elapsed } from "../components/relativeTime.js";
|
|
12
13
|
import { RunStatusIcon } from "../components/RunStatusIcon.js";
|
|
13
|
-
import { useStickyTop } from "../components/stickyTop.js";
|
|
14
14
|
import { Tabs } from "../components/Tabs.js";
|
|
15
15
|
import { LogViewer } from "../logviewer/LogViewer.js";
|
|
16
16
|
import { initializeValues } from "../form/lib.js";
|
|
@@ -3,12 +3,12 @@ import { buttonClasses } from "../components/buttonClasses.js";
|
|
|
3
3
|
import { MoreIcon } from "../icons.js";
|
|
4
4
|
import { MenuPopup, MenuSeparator } from "../components/Menu.js";
|
|
5
5
|
import { isDev } from "../dev.js";
|
|
6
|
+
import { useStickyTop } from "../components/stickyTop.js";
|
|
6
7
|
import { Loader } from "../components/Loader.js";
|
|
7
8
|
import { Tooltip } from "../components/Tooltip.js";
|
|
8
9
|
import { useStrings } from "../components/Provider.js";
|
|
9
10
|
import { AppLink } from "../components/AppLink.js";
|
|
10
11
|
import { MenuEntries, useRunAction, warnIfInert } from "../components/MenuEntries.js";
|
|
11
|
-
import { useStickyTop } from "../components/stickyTop.js";
|
|
12
12
|
import { Menu } from "@base-ui/react/menu";
|
|
13
13
|
import { Fragment, cloneElement, createContext, isValidElement, use, useLayoutEffect, useRef, useState } from "react";
|
|
14
14
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
package/dist/list/ListPage.d.ts
CHANGED
|
@@ -80,6 +80,30 @@ export declare function ListRow({ href, onActivate, items, goTo, openMenu, pinne
|
|
|
80
80
|
leading?: ReactNode;
|
|
81
81
|
children: ReactNode;
|
|
82
82
|
}): import("react").JSX.Element;
|
|
83
|
+
/**
|
|
84
|
+
* A group's header row in a list: its label, such as a status or a cluster,
|
|
85
|
+
* and, against the row's far end, what sums it up, such as "2/3 running".
|
|
86
|
+
*
|
|
87
|
+
* Inside a `ListPage` with a view, it has one cell per column, each hiding
|
|
88
|
+
* with its column as the list narrows, so a group never holds a hidden
|
|
89
|
+
* column's width open; its content lays out across the row's visible width
|
|
90
|
+
* from the first cell. Elsewhere it is one cell spanning `colSpan` columns.
|
|
91
|
+
*/
|
|
92
|
+
export declare function GroupRow({ label, count, trailing, children, leading, colSpan, className, }: {
|
|
93
|
+
/** The group's name: text, or an icon, a name with its tooltip, badges. */
|
|
94
|
+
label?: ReactNode;
|
|
95
|
+
/** How many rows the group holds, shown after the label. */
|
|
96
|
+
count?: number | undefined;
|
|
97
|
+
/** Set against the row's far end, such as "2/3 running". */
|
|
98
|
+
trailing?: ReactNode;
|
|
99
|
+
/** The whole header, laid out in a row, in place of `label`, `count` and `trailing`. */
|
|
100
|
+
children?: ReactNode;
|
|
101
|
+
/** Cells the list's rows have before its columns, such as a selection checkbox. */
|
|
102
|
+
leading?: number | undefined;
|
|
103
|
+
/** Outside a list view, the columns the row spans. */
|
|
104
|
+
colSpan?: number | undefined;
|
|
105
|
+
className?: string | undefined;
|
|
106
|
+
}): import("react").JSX.Element;
|
|
83
107
|
/** Name column min-width is by role, not position, so a leading checkbox
|
|
84
108
|
* doesn't shift it. A column with an `orderBy` the view offers sorts by it.
|
|
85
109
|
* While the view's column headers are off, the header row collapses: its
|
package/dist/list/ListPage.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Tooltip } from "../components/Tooltip.js";
|
|
|
4
4
|
import { useNavigation } from "../components/Provider.js";
|
|
5
5
|
import { AppLink, isPlainClick } from "../components/AppLink.js";
|
|
6
6
|
import { isMenuDivider, isMenuGroup, isMenuSubmenu, useRunAction, warnIfInert } from "../components/MenuEntries.js";
|
|
7
|
-
import { Table } from "../components/Table.js";
|
|
7
|
+
import { CellPlacementContext, STACKED_ITEM, Table } from "../components/Table.js";
|
|
8
8
|
import { ListFilterBar } from "./ListFilterBar.js";
|
|
9
9
|
import { ListActionsHeader, ListError, ListSkeleton, StretchRowLinkContext } from "./ListTable.js";
|
|
10
10
|
import { MoreButton, rowContextMenuProps, useRowMenuActive } from "./rowMenu.js";
|
|
@@ -85,28 +85,51 @@ function ListPage({ bleed = false, toolbar, filterBar, view, loading = false, er
|
|
|
85
85
|
]
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
|
+
var STACKED = { stacked: true };
|
|
89
|
+
var STACKED_LINE = "mt-1 flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1 empty:hidden @[36rem]:hidden";
|
|
90
|
+
var isCellElement = (child) => child.type === Table.Item || child.type === "td";
|
|
88
91
|
function ListRow({ href, onActivate, items, goTo, openMenu, pinnedActions, selected, selectionActive, onRowSelect, leading, className, children }) {
|
|
89
92
|
const { active: menuActive, openMenu: openRowMenu } = useRowMenuActive(openMenu);
|
|
90
93
|
const { show: showActions, always: alwaysShowActions, columnClasses, columnStacks } = useContext(RowActionsContext);
|
|
91
94
|
const cells = Children.toArray(children);
|
|
92
95
|
const stacked = cells.flatMap((child, i) => {
|
|
93
96
|
if (i === 0 || !columnStacks[i] || !isValidElement(child)) return [];
|
|
94
|
-
|
|
95
|
-
|
|
97
|
+
if (isCellElement(child)) {
|
|
98
|
+
const content = child.props.children;
|
|
99
|
+
return Children.toArray(content).length > 0 ? [child] : [];
|
|
100
|
+
}
|
|
101
|
+
return [child];
|
|
96
102
|
});
|
|
103
|
+
const under = stacked.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
104
|
+
className: STACKED_LINE,
|
|
105
|
+
"data-stacked-line": true,
|
|
106
|
+
children: stacked.map((child, j) => isValidElement(child) && child.type === "td" ? /* @__PURE__ */ jsx("div", {
|
|
107
|
+
className: STACKED_ITEM,
|
|
108
|
+
"data-stacked": true,
|
|
109
|
+
children: child.props.children
|
|
110
|
+
}, j) : /* @__PURE__ */ jsx(CellPlacementContext, {
|
|
111
|
+
value: STACKED,
|
|
112
|
+
children: child
|
|
113
|
+
}, j))
|
|
114
|
+
}) : void 0;
|
|
97
115
|
const styledCells = cells.map((child, i) => {
|
|
98
116
|
const cls = cx(columnClasses[i], i > 0 && stacked.length > 0 && !columnStacks[i] && STAYS_BESIDE_STACK);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
className:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
117
|
+
const after = i === 0 ? under : void 0;
|
|
118
|
+
if (!isValidElement(child) || !cls && !after) return child;
|
|
119
|
+
if (child.type === "td") {
|
|
120
|
+
const el = child;
|
|
121
|
+
return cloneElement(el, {
|
|
122
|
+
className: cx(el.props.className, cls),
|
|
123
|
+
...after ? { children: /* @__PURE__ */ jsxs(Fragment$1, { children: [el.props.children, after] }) } : {}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return /* @__PURE__ */ jsx(CellPlacementContext, {
|
|
127
|
+
value: {
|
|
128
|
+
className: cls || void 0,
|
|
129
|
+
after
|
|
130
|
+
},
|
|
131
|
+
children: child
|
|
132
|
+
}, child.key);
|
|
110
133
|
});
|
|
111
134
|
const floating = !alwaysShowActions && !(pinnedActions && pinnedActions.length > 0);
|
|
112
135
|
const stretch = !!href && !selectionActive;
|
|
@@ -133,7 +156,7 @@ function ListRow({ href, onActivate, items, goTo, openMenu, pinnedActions, selec
|
|
|
133
156
|
children: styledCells
|
|
134
157
|
}),
|
|
135
158
|
showActions && /* @__PURE__ */ jsx(Table.Item, {
|
|
136
|
-
className: cx("relative py-2.5", floating && "pointer-fine:w-0! pointer-fine:min-w-0! pointer-fine:pl-0! pointer-fine:pr-1!"),
|
|
159
|
+
className: cx("relative overflow-visible py-2.5", floating && "pointer-fine:w-0! pointer-fine:min-w-0! pointer-fine:pl-0! pointer-fine:pr-1!"),
|
|
137
160
|
children: /* @__PURE__ */ jsxs("div", {
|
|
138
161
|
className: cx("absolute inset-y-0 right-0 flex items-center gap-0.5 pr-4", floating && !menuActive && "pointer-fine:opacity-0 pointer-fine:group-hover:opacity-100 pointer-fine:focus-within:opacity-100", floating && "pointer-fine:bg-linear-to-l pointer-fine:from-(--theme-hover) pointer-fine:from-70% pointer-fine:to-transparent pointer-fine:pl-6"),
|
|
139
162
|
children: [pinnedActions && pinnedActions.length > 0 && /* @__PURE__ */ jsx("span", {
|
|
@@ -153,6 +176,68 @@ function ListRow({ href, onActivate, items, goTo, openMenu, pinnedActions, selec
|
|
|
153
176
|
]
|
|
154
177
|
});
|
|
155
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* A group's header row in a list: its label, such as a status or a cluster,
|
|
181
|
+
* and, against the row's far end, what sums it up, such as "2/3 running".
|
|
182
|
+
*
|
|
183
|
+
* Inside a `ListPage` with a view, it has one cell per column, each hiding
|
|
184
|
+
* with its column as the list narrows, so a group never holds a hidden
|
|
185
|
+
* column's width open; its content lays out across the row's visible width
|
|
186
|
+
* from the first cell. Elsewhere it is one cell spanning `colSpan` columns.
|
|
187
|
+
*/
|
|
188
|
+
function GroupRow({ label, count, trailing, children, leading = 0, colSpan, className }) {
|
|
189
|
+
const { show: showActions, columnClasses } = useContext(RowActionsContext);
|
|
190
|
+
const content = children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
191
|
+
/* @__PURE__ */ jsx("span", {
|
|
192
|
+
className: "flex min-w-0 items-center gap-2 text-(--theme-app)",
|
|
193
|
+
children: label
|
|
194
|
+
}),
|
|
195
|
+
count !== void 0 && /* @__PURE__ */ jsxs("span", {
|
|
196
|
+
className: "shrink-0 text-(--theme-muted-text-color)",
|
|
197
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
198
|
+
className: "mx-1.5 opacity-50",
|
|
199
|
+
"aria-hidden": "true",
|
|
200
|
+
children: "·"
|
|
201
|
+
}), count]
|
|
202
|
+
}),
|
|
203
|
+
trailing !== void 0 && trailing !== null && trailing !== false && /* @__PURE__ */ jsx("span", {
|
|
204
|
+
className: "ml-auto flex shrink-0 items-center gap-2 pl-3 text-(--theme-muted-text-color) tabular-nums",
|
|
205
|
+
children: trailing
|
|
206
|
+
})
|
|
207
|
+
] });
|
|
208
|
+
const line = "flex h-8 min-w-0 items-center text-xs font-medium whitespace-nowrap";
|
|
209
|
+
const row = cx("bg-(--theme-muted-panel-bg)", className);
|
|
210
|
+
if (columnClasses.length === 0) return /* @__PURE__ */ jsx("tr", {
|
|
211
|
+
className: row,
|
|
212
|
+
children: /* @__PURE__ */ jsx("td", {
|
|
213
|
+
colSpan,
|
|
214
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
215
|
+
className: line,
|
|
216
|
+
children: content
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
});
|
|
220
|
+
const cells = leading + columnClasses.length + (showActions ? 1 : 0);
|
|
221
|
+
return /* @__PURE__ */ jsx("tr", {
|
|
222
|
+
className: row,
|
|
223
|
+
"data-group-row": true,
|
|
224
|
+
children: Array.from({ length: cells }, (_, i) => {
|
|
225
|
+
const column = i - leading;
|
|
226
|
+
const cls = column >= 0 && column < columnClasses.length ? columnClasses[column] : void 0;
|
|
227
|
+
return /* @__PURE__ */ jsx("td", {
|
|
228
|
+
className: cx("p-0", cls),
|
|
229
|
+
children: i === 0 && /* @__PURE__ */ jsx("div", {
|
|
230
|
+
className: "w-0",
|
|
231
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
232
|
+
className: line,
|
|
233
|
+
style: { width: "calc(100cqw - 2rem)" },
|
|
234
|
+
children: content
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
}, i);
|
|
238
|
+
})
|
|
239
|
+
});
|
|
240
|
+
}
|
|
156
241
|
function PinnedActionButton({ action, visible }) {
|
|
157
242
|
warnIfInert(action, "ListPage pinned action");
|
|
158
243
|
const runAction = useRunAction();
|
|
@@ -198,7 +283,10 @@ function ListColumns({ view }) {
|
|
|
198
283
|
return /* @__PURE__ */ jsx(Table.Header, {
|
|
199
284
|
caps: false,
|
|
200
285
|
className: cx(col.headerClassName, view.columnClasses[i], !view.showColumnHeaders && "list-headers-off"),
|
|
201
|
-
style: col.alwaysVisible
|
|
286
|
+
style: col.alwaysVisible || col.width !== void 0 ? {
|
|
287
|
+
...col.alwaysVisible && { minWidth: 200 },
|
|
288
|
+
...col.width !== void 0 && { width: col.width }
|
|
289
|
+
} : void 0,
|
|
202
290
|
sort: orderBy === void 0 ? void 0 : columnSort(view, orderBy),
|
|
203
291
|
onSort: orderBy === void 0 ? void 0 : () => view.sortBy(orderBy),
|
|
204
292
|
children: col.label
|
|
@@ -211,4 +299,4 @@ function useListNavigate() {
|
|
|
211
299
|
return useNavigation().goTo;
|
|
212
300
|
}
|
|
213
301
|
//#endregion
|
|
214
|
-
export { ListColumns, ListPage, ListRow, ListRowActionsProvider, useListNavigate };
|
|
302
|
+
export { GroupRow, ListColumns, ListPage, ListRow, ListRowActionsProvider, useListNavigate };
|
package/dist/list/ListTable.d.ts
CHANGED
|
@@ -54,17 +54,13 @@ export declare function NameCell({ icon, iconTile, name, subtitle, href, badge,
|
|
|
54
54
|
}): import("react").JSX.Element;
|
|
55
55
|
/** A row's column text: muted 13px, or monospace for an ID, a path or a
|
|
56
56
|
* hash. The style sits on the text rather than the cell, so it carries over
|
|
57
|
-
* when a narrow list stacks the column under the name.
|
|
57
|
+
* when a narrow list stacks the column under the name. Text wider than its
|
|
58
|
+
* column is cut off with an ellipsis, and shows whole in a tooltip. */
|
|
58
59
|
export declare function ListCellText({ mono, className, children, }: {
|
|
59
60
|
mono?: boolean | undefined;
|
|
60
61
|
className?: string | undefined;
|
|
61
62
|
children: ReactNode;
|
|
62
63
|
}): import("react").JSX.Element;
|
|
63
|
-
export declare function GroupRow({ label, count, colSpan, }: {
|
|
64
|
-
label: string;
|
|
65
|
-
count: number;
|
|
66
|
-
colSpan: number;
|
|
67
|
-
}): import("react").JSX.Element;
|
|
68
64
|
/** Says a list couldn't load. It never shows the error's own text, which is
|
|
69
65
|
* the server's words, in its language and not written for the reader. */
|
|
70
66
|
export declare function ListError({ noun, message, }: {
|
package/dist/list/ListTable.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
2
|
import { ErrorIcon } from "../icons.js";
|
|
3
|
+
import { Tooltip } from "../components/Tooltip.js";
|
|
3
4
|
import { useStrings } from "../components/Provider.js";
|
|
4
5
|
import { AppLink } from "../components/AppLink.js";
|
|
5
6
|
import { positionKeys } from "../components/keys.js";
|
|
6
7
|
import { LoadingRegion, Skeleton } from "../components/Skeleton.js";
|
|
7
8
|
import { Checkbox } from "../components/Checkbox.js";
|
|
8
|
-
import { Table } from "../components/Table.js";
|
|
9
|
+
import { CellPlacementContext, Table } from "../components/Table.js";
|
|
9
10
|
import { createContext, useContext } from "react";
|
|
10
11
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
11
12
|
//#region src/list/ListTable.tsx
|
|
@@ -43,6 +44,8 @@ var STRETCHED_LINK = "after:absolute after:inset-0 after:content-[''] focus-visi
|
|
|
43
44
|
var STRETCHED_LINK_ROW = "relative [&_:is(a,button,input,label,[role=checkbox]):not([data-row-link])]:relative [&_:is(a,button,input,label,[role=checkbox]):not([data-row-link])]:z-[1]";
|
|
44
45
|
function NameCell({ icon, iconTile = false, name, subtitle, href, badge, title, children }) {
|
|
45
46
|
const stretch = useContext(StretchRowLinkContext);
|
|
47
|
+
const placed = useContext(CellPlacementContext);
|
|
48
|
+
const under = placed?.after;
|
|
46
49
|
const hasIcon = icon !== null && icon !== void 0 && icon !== false;
|
|
47
50
|
const columns = hasIcon ? "col-span-2 grid grid-cols-subgrid items-center" : "flex items-center";
|
|
48
51
|
const inner = /* @__PURE__ */ jsxs(Fragment$1, { children: [hasIcon && (iconTile && subtitle ? /* @__PURE__ */ jsx("span", {
|
|
@@ -65,7 +68,7 @@ function NameCell({ icon, iconTile = false, name, subtitle, href, badge, title,
|
|
|
65
68
|
children: subtitle
|
|
66
69
|
})]
|
|
67
70
|
})] });
|
|
68
|
-
|
|
71
|
+
const cell = /* @__PURE__ */ jsx(Table.Item, {
|
|
69
72
|
className: "py-2.5",
|
|
70
73
|
children: /* @__PURE__ */ jsxs("div", {
|
|
71
74
|
className: cx("grid min-w-0", hasIcon ? "grid-cols-[auto_minmax(0,1fr)] gap-x-2.5" : "grid-cols-[minmax(0,1fr)]"),
|
|
@@ -77,41 +80,34 @@ function NameCell({ icon, iconTile = false, name, subtitle, href, badge, title,
|
|
|
77
80
|
}) : /* @__PURE__ */ jsx("div", {
|
|
78
81
|
className: cx("min-w-0", columns),
|
|
79
82
|
children: inner
|
|
80
|
-
}), !!children && /* @__PURE__ */
|
|
83
|
+
}), (!!children || !!under) && /* @__PURE__ */ jsxs("div", {
|
|
81
84
|
className: hasIcon ? "col-start-2 min-w-0" : "min-w-0",
|
|
82
|
-
children
|
|
85
|
+
children: [children, under]
|
|
83
86
|
})]
|
|
84
87
|
})
|
|
85
88
|
});
|
|
89
|
+
return placed?.after ? /* @__PURE__ */ jsx(CellPlacementContext, {
|
|
90
|
+
value: {
|
|
91
|
+
...placed,
|
|
92
|
+
after: void 0
|
|
93
|
+
},
|
|
94
|
+
children: cell
|
|
95
|
+
}) : cell;
|
|
86
96
|
}
|
|
87
97
|
/** A row's column text: muted 13px, or monospace for an ID, a path or a
|
|
88
98
|
* hash. The style sits on the text rather than the cell, so it carries over
|
|
89
|
-
* when a narrow list stacks the column under the name.
|
|
99
|
+
* when a narrow list stacks the column under the name. Text wider than its
|
|
100
|
+
* column is cut off with an ellipsis, and shows whole in a tooltip. */
|
|
90
101
|
function ListCellText({ mono = false, className, children }) {
|
|
91
|
-
|
|
92
|
-
className: cx("text-(--theme-muted-text-color)", mono ? "font-mono text-xs" : "text-[13px]", className),
|
|
102
|
+
const text = /* @__PURE__ */ jsx("span", {
|
|
103
|
+
className: cx("block min-w-0 truncate text-(--theme-muted-text-color)", mono ? "font-mono text-xs" : "text-[13px]", className),
|
|
93
104
|
children
|
|
94
105
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
children: [
|
|
101
|
-
/* @__PURE__ */ jsx("span", {
|
|
102
|
-
className: "text-(--theme-app)",
|
|
103
|
-
children: label
|
|
104
|
-
}),
|
|
105
|
-
/* @__PURE__ */ jsx("span", {
|
|
106
|
-
className: "mx-1.5 opacity-50",
|
|
107
|
-
children: "·"
|
|
108
|
-
}),
|
|
109
|
-
/* @__PURE__ */ jsx("span", {
|
|
110
|
-
className: "text-(--theme-muted-text-color)",
|
|
111
|
-
children: count
|
|
112
|
-
})
|
|
113
|
-
]
|
|
114
|
-
}) });
|
|
106
|
+
return typeof children === "string" || typeof children === "number" ? /* @__PURE__ */ jsx(Tooltip, {
|
|
107
|
+
content: children,
|
|
108
|
+
overflowOnly: true,
|
|
109
|
+
children: text
|
|
110
|
+
}) : text;
|
|
115
111
|
}
|
|
116
112
|
/** Says a list couldn't load. It never shows the error's own text, which is
|
|
117
113
|
* the server's words, in its language and not written for the reader. */
|
|
@@ -235,4 +231,4 @@ function ListActionsHeader({ view, actionCount = 0, className }) {
|
|
|
235
231
|
}
|
|
236
232
|
ListActionsHeader.isTableHeader = true;
|
|
237
233
|
//#endregion
|
|
238
|
-
export { FLOATING_ACTIONS_LANE,
|
|
234
|
+
export { FLOATING_ACTIONS_LANE, HEADERS_OFF, ListActionsHeader, ListCellText, ListError, ListSelectAllHeader, ListSkeleton, NAME_MIN_WIDTH_PX, NameCell, RowSelectCheckbox, STRETCHED_LINK, STRETCHED_LINK_ROW, StretchRowLinkContext, actionsColumnWidth, listTableFixedProps, listTableProps };
|