@parallelworks/ui 0.84.1 → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/components/CopyButton.d.ts +7 -2
- package/dist/components/CopyButton.js +11 -52
- package/dist/components/PageHeaderAction.d.ts +12 -5
- package/dist/components/PageHeaderAction.js +10 -9
- package/dist/components/Pagination.d.ts +4 -2
- package/dist/components/Pagination.js +54 -17
- package/dist/components/Provider.d.ts +4 -0
- package/dist/components/Provider.js +5 -1
- package/dist/components/Table.d.ts +3 -0
- package/dist/components/Table.js +6 -3
- package/dist/components/Tabs.js +31 -2
- package/dist/components/Tag.d.ts +2 -1
- package/dist/components/Tag.js +5 -4
- package/dist/components/chart.d.ts +57 -10
- package/dist/components/chart.js +117 -22
- package/dist/components/scrollFade.d.ts +13 -0
- package/dist/components/scrollFade.js +51 -0
- package/dist/file-explorer/FileExplorer.js +8 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/list/ListPage.d.ts +27 -0
- package/dist/list/ListPage.js +172 -20
- package/dist/list/ListTable.d.ts +3 -6
- package/dist/list/ListTable.js +12 -25
- package/dist/list/ListViewControls.d.ts +1 -1
- package/dist/list/ListViewControls.js +2 -1
- package/dist/list/index.d.ts +2 -2
- package/dist/list/index.js +4 -4
- package/dist/list/listView.d.ts +19 -1
- package/dist/list/listView.js +11 -4
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ Each need has one component. Reach for it before styling markup by hand.
|
|
|
125
125
|
| A status in words | `StatusBadge`, or `StatusDot` beside text that says it |
|
|
126
126
|
| A thing's tags or labels | `Tag`; `TagList` for several, wrapped or on one line ending in "+N" |
|
|
127
127
|
| Keys to press | `Kbd`, with `Mod` for Command or Control; `ariaKeyShortcuts` for the control the keys press |
|
|
128
|
-
| A chart's colors, legend and tooltip | `seriesColors
|
|
128
|
+
| A chart's colors, legend and tooltip | `seriesColors` (by each series' place in a stable key list, eight at most) and `foldSeries` (the rest into "Other"), `ChartLegendList`, `ChartTooltipContent` placed beside the crosshair with `ChartTooltipBeside` or `chartTooltipPlacement`, and `useSeriesHighlight`, around a chart drawn by hand or by a library |
|
|
129
129
|
| A run's, job's or step's status beside its name | `RunStatusIcon`; `StatusBadge` with `runStatusTone` to say it in words |
|
|
130
130
|
| A copy action | `CopyButton`; `CopyCodeBlock` for a command or snippet, with `secret` for a value shown once; `SelectionCopy` to copy part of a block by selecting it; `useCopy` for a menu row or a control of your own |
|
|
131
131
|
| A live relative time or duration | `RelativeTime` (with a `fallback`, an `exact` style for its tooltip, or `show="absolute"`), `Elapsed` |
|
|
@@ -48,11 +48,16 @@ export interface CopyCodeBlockProps {
|
|
|
48
48
|
* surface reads on a panel and a tinted callout alike, and the buttons stay right after the
|
|
49
49
|
* value, always shown. The copy confirms with its check alone, as the change shows there. */
|
|
50
50
|
inline?: boolean | undefined;
|
|
51
|
+
/** False also says "Copied" in a toast. By default the copy confirms with the button's
|
|
52
|
+
* check alone, which stays in view until it turns back, as the button is where the reader
|
|
53
|
+
* pressed; a failure toasts either way. */
|
|
54
|
+
quiet?: boolean | undefined;
|
|
51
55
|
className?: string | undefined;
|
|
52
56
|
}
|
|
53
57
|
/** A block of code, such as a command to run, with a button that copies it. The
|
|
54
58
|
* buttons show while the pointer is over the block or focus is in it, and always
|
|
55
|
-
* where there is no hover to reveal them.
|
|
59
|
+
* where there is no hover to reveal them. A copy confirms with the button's check,
|
|
60
|
+
* not a toast; a failure still toasts. With `inline` it is a compact value in the line
|
|
56
61
|
* instead, such as a token shown once, its buttons always beside it. With `secret` it
|
|
57
62
|
* masks the value until the eye shows it, and copies it either way. */
|
|
58
|
-
export declare function CopyCodeBlock({ text, label, children, secret, revealed: revealedProp, defaultRevealed, onRevealedChange, inline, wrap, className, }: CopyCodeBlockProps): import("react").JSX.Element;
|
|
63
|
+
export declare function CopyCodeBlock({ text, label, children, secret, revealed: revealedProp, defaultRevealed, onRevealedChange, inline, wrap, quiet, className, }: CopyCodeBlockProps): import("react").JSX.Element;
|
|
@@ -4,7 +4,8 @@ import { CheckIcon, CopyIcon, EyeIcon, EyeOffIcon } from "../icons.js";
|
|
|
4
4
|
import { useStrings } from "./Provider.js";
|
|
5
5
|
import { useCopy } from "./useCopy.js";
|
|
6
6
|
import { IconButton } from "./IconButton.js";
|
|
7
|
-
import {
|
|
7
|
+
import { fadeMask, useHiddenEnds } from "./scrollFade.js";
|
|
8
|
+
import { useState } from "react";
|
|
8
9
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
9
10
|
//#region src/components/CopyButton.tsx
|
|
10
11
|
/** The one copy control: a button that copies its text, turns into a check for a
|
|
@@ -23,6 +24,7 @@ function CopyButton({ text, label, quiet = false, showLabel = false, variant = s
|
|
|
23
24
|
if (showLabel) return /* @__PURE__ */ jsxs("button", {
|
|
24
25
|
type: "button",
|
|
25
26
|
...props,
|
|
27
|
+
"data-copied": copied || void 0,
|
|
26
28
|
"aria-label": props["aria-label"] ?? name,
|
|
27
29
|
onClick,
|
|
28
30
|
className: cx(buttonClasses({
|
|
@@ -33,6 +35,7 @@ function CopyButton({ text, label, quiet = false, showLabel = false, variant = s
|
|
|
33
35
|
});
|
|
34
36
|
return /* @__PURE__ */ jsx(IconButton, {
|
|
35
37
|
...props,
|
|
38
|
+
"data-copied": copied || void 0,
|
|
36
39
|
variant,
|
|
37
40
|
size,
|
|
38
41
|
label: name,
|
|
@@ -40,60 +43,14 @@ function CopyButton({ text, label, quiet = false, showLabel = false, variant = s
|
|
|
40
43
|
onClick
|
|
41
44
|
});
|
|
42
45
|
}
|
|
43
|
-
var NO_ENDS = {
|
|
44
|
-
start: false,
|
|
45
|
-
end: false
|
|
46
|
-
};
|
|
47
|
-
function useHiddenEnds(active) {
|
|
48
|
-
const ref = useRef(null);
|
|
49
|
-
const [ends, setEnds] = useState(NO_ENDS);
|
|
50
|
-
const measureNow = () => {
|
|
51
|
-
const el = ref.current;
|
|
52
|
-
if (!el || !active) {
|
|
53
|
-
setEnds(NO_ENDS);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
const scrolled = Math.abs(el.scrollLeft);
|
|
57
|
-
const start = scrolled > 1;
|
|
58
|
-
const end = scrolled + el.clientWidth < el.scrollWidth - 1;
|
|
59
|
-
setEnds((was) => was.start === start && was.end === end ? was : {
|
|
60
|
-
start,
|
|
61
|
-
end
|
|
62
|
-
});
|
|
63
|
-
};
|
|
64
|
-
const measure = useEffectEvent(measureNow);
|
|
65
|
-
useLayoutEffect(() => {
|
|
66
|
-
measure();
|
|
67
|
-
});
|
|
68
|
-
useLayoutEffect(() => {
|
|
69
|
-
const el = ref.current;
|
|
70
|
-
if (!el || !active || typeof ResizeObserver === "undefined") return;
|
|
71
|
-
const observer = new ResizeObserver(() => measure());
|
|
72
|
-
observer.observe(el);
|
|
73
|
-
return () => observer.disconnect();
|
|
74
|
-
}, [active]);
|
|
75
|
-
return {
|
|
76
|
-
ref,
|
|
77
|
-
ends,
|
|
78
|
-
onScroll: measureNow
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
var FADE = "1.25rem";
|
|
82
|
-
function fadeMask({ start, end }) {
|
|
83
|
-
if (!start && !end) return;
|
|
84
|
-
const image = `linear-gradient(to var(--fade-end, right), ${start ? `transparent, #000 ${FADE}` : "#000"}, ${end ? `#000 calc(100% - ${FADE}), transparent` : "#000"})`;
|
|
85
|
-
return {
|
|
86
|
-
maskImage: image,
|
|
87
|
-
WebkitMaskImage: image
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
46
|
var MASK = "•".repeat(24);
|
|
91
47
|
/** A block of code, such as a command to run, with a button that copies it. The
|
|
92
48
|
* buttons show while the pointer is over the block or focus is in it, and always
|
|
93
|
-
* where there is no hover to reveal them.
|
|
49
|
+
* where there is no hover to reveal them. A copy confirms with the button's check,
|
|
50
|
+
* not a toast; a failure still toasts. With `inline` it is a compact value in the line
|
|
94
51
|
* instead, such as a token shown once, its buttons always beside it. With `secret` it
|
|
95
52
|
* masks the value until the eye shows it, and copies it either way. */
|
|
96
|
-
function CopyCodeBlock({ text, label, children, secret = false, revealed: revealedProp, defaultRevealed = false, onRevealedChange, inline = false, wrap = false, className }) {
|
|
53
|
+
function CopyCodeBlock({ text, label, children, secret = false, revealed: revealedProp, defaultRevealed = false, onRevealedChange, inline = false, wrap = false, quiet = true, className }) {
|
|
97
54
|
const { common: t } = useStrings();
|
|
98
55
|
const [ownRevealed, setOwnRevealed] = useState(defaultRevealed);
|
|
99
56
|
const revealed = revealedProp ?? ownRevealed;
|
|
@@ -102,6 +59,7 @@ function CopyCodeBlock({ text, label, children, secret = false, revealed: reveal
|
|
|
102
59
|
onRevealedChange?.(next);
|
|
103
60
|
};
|
|
104
61
|
const reveal = "opacity-0 group-hover/code:opacity-100 group-focus-within/code:opacity-100 pointer-coarse:opacity-100";
|
|
62
|
+
const revealCopy = `${reveal} data-copied:opacity-100`;
|
|
105
63
|
let shown = children ?? text;
|
|
106
64
|
if (secret && !revealed && children === void 0) shown = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
107
65
|
"aria-hidden": "true",
|
|
@@ -140,7 +98,7 @@ function CopyCodeBlock({ text, label, children, secret = false, revealed: reveal
|
|
|
140
98
|
text,
|
|
141
99
|
label,
|
|
142
100
|
size: "xs",
|
|
143
|
-
quiet
|
|
101
|
+
quiet
|
|
144
102
|
}), revealButton]
|
|
145
103
|
})]
|
|
146
104
|
});
|
|
@@ -154,7 +112,8 @@ function CopyCodeBlock({ text, label, children, secret = false, revealed: reveal
|
|
|
154
112
|
children: [revealButton, /* @__PURE__ */ jsx(CopyButton, {
|
|
155
113
|
text,
|
|
156
114
|
label,
|
|
157
|
-
|
|
115
|
+
quiet,
|
|
116
|
+
className: cx("bg-(--theme-muted-panel-bg)", revealCopy)
|
|
158
117
|
})]
|
|
159
118
|
})]
|
|
160
119
|
});
|
|
@@ -35,8 +35,15 @@ export type PageHeaderActionProps = Target & {
|
|
|
35
35
|
disabledReason?: string | undefined;
|
|
36
36
|
/** A hint shown on hover and focus while it can be pressed. */
|
|
37
37
|
tooltip?: string | undefined;
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
38
|
+
/** How a labeled action reads in the bar: `outline`, the page's small
|
|
39
|
+
* secondary button, as "+ Add" is; or `link`, text in the link color, for a
|
|
40
|
+
* page that keeps the older look. On a narrow bar the label folds into the
|
|
41
|
+
* tooltip and the outline stays, so the action is an outlined icon. An
|
|
42
|
+
* `iconOnly` action, such as a pencil beside the title, is always bare. */
|
|
43
|
+
variant?: 'outline' | 'link' | undefined;
|
|
44
|
+
/** For an action that removes or breaks something: in the error color where
|
|
45
|
+
* it is a menu entry, in the header's "…" menu. In the bar it reads as the
|
|
46
|
+
* others do; a page's destructive actions belong in its menu. */
|
|
40
47
|
destructive?: boolean | undefined;
|
|
41
48
|
/** A toggle's state, such as a favorite: set, it reads as pressed. Buttons only. */
|
|
42
49
|
pressed?: boolean | undefined;
|
|
@@ -47,10 +54,10 @@ export type PageHeaderActionProps = Target & {
|
|
|
47
54
|
/**
|
|
48
55
|
* An action in the page header: a link (`href`, `external` for a new tab,
|
|
49
56
|
* `reloadDocument` for a path the router doesn't serve), a
|
|
50
|
-
* button (`onClick`) or a menu (`menu`).
|
|
51
|
-
*
|
|
57
|
+
* button (`onClick`) or a menu (`menu`). A labeled action is a small outline
|
|
58
|
+
* button, as "+ Add" is, so the page's commands stand apart from the trail.
|
|
52
59
|
*/
|
|
53
|
-
export declare function PageHeaderAction({ icon: Icon, label, ariaLabel, iconOnly, disabled, disabledReason, tooltip, destructive, pressed, loading, ...target }: PageHeaderActionProps): import("react").JSX.Element;
|
|
60
|
+
export declare function PageHeaderAction({ icon: Icon, label, ariaLabel, iconOnly, variant, disabled, disabledReason, tooltip, destructive, pressed, loading, ...target }: PageHeaderActionProps): import("react").JSX.Element;
|
|
54
61
|
export type PageHeaderAddButtonProps = Target & {
|
|
55
62
|
/** The full action, such as "New cluster": the tooltip and accessible name. */
|
|
56
63
|
label: string;
|
|
@@ -55,11 +55,16 @@ function HeaderControl({ name, tooltip, disabled, disabledReason, loading = fals
|
|
|
55
55
|
/**
|
|
56
56
|
* An action in the page header: a link (`href`, `external` for a new tab,
|
|
57
57
|
* `reloadDocument` for a path the router doesn't serve), a
|
|
58
|
-
* button (`onClick`) or a menu (`menu`).
|
|
59
|
-
*
|
|
58
|
+
* button (`onClick`) or a menu (`menu`). A labeled action is a small outline
|
|
59
|
+
* button, as "+ Add" is, so the page's commands stand apart from the trail.
|
|
60
60
|
*/
|
|
61
|
-
function PageHeaderAction({ icon: Icon, label, ariaLabel, iconOnly = false, disabled = false, disabledReason, tooltip, destructive = false, pressed, loading = false, ...target }) {
|
|
62
|
-
const
|
|
61
|
+
function PageHeaderAction({ icon: Icon, label, ariaLabel, iconOnly = false, variant = "outline", disabled = false, disabledReason, tooltip, destructive = false, pressed, loading = false, ...target }) {
|
|
62
|
+
const link = variant === "link" && !iconOnly;
|
|
63
|
+
const className = cx(buttonClasses({
|
|
64
|
+
variant: link || iconOnly ? "ghost" : "outline",
|
|
65
|
+
size: "xs",
|
|
66
|
+
iconOnly
|
|
67
|
+
}), link && "*:text-(--theme-link)");
|
|
63
68
|
const icon = loading ? /* @__PURE__ */ jsx(Loader, {
|
|
64
69
|
inline: true,
|
|
65
70
|
decorative: true,
|
|
@@ -83,11 +88,7 @@ function PageHeaderAction({ icon: Icon, label, ariaLabel, iconOnly = false, disa
|
|
|
83
88
|
disabledReason,
|
|
84
89
|
loading,
|
|
85
90
|
pressed,
|
|
86
|
-
className
|
|
87
|
-
variant: "ghost",
|
|
88
|
-
size: "xs",
|
|
89
|
-
iconOnly
|
|
90
|
-
}), tone),
|
|
91
|
+
className,
|
|
91
92
|
children: [icon, !iconOnly && /* @__PURE__ */ jsx("span", {
|
|
92
93
|
className: "@max-md:sr-only",
|
|
93
94
|
children: label
|
|
@@ -23,8 +23,10 @@ export interface PaginationProps {
|
|
|
23
23
|
/** Numbers the pages between previous and next, for a list a reader jumps
|
|
24
24
|
* around in, such as a catalog: the first and last, and a window around
|
|
25
25
|
* the current one, with gaps between. `max` is how many steps show at
|
|
26
|
-
* most, gaps included (7 by default, 5 at least). Needs a `total
|
|
27
|
-
* on a phone
|
|
26
|
+
* most, gaps included (7 by default, 5 at least). Needs a `total`. Where
|
|
27
|
+
* the bar is narrow, as on a phone, the window narrows to the first, the
|
|
28
|
+
* current and the last page (5 steps), and where even those won't fit, to
|
|
29
|
+
* the current page and the count: "5 / 12". */
|
|
28
30
|
pageNumbers?: boolean | {
|
|
29
31
|
max?: number | undefined;
|
|
30
32
|
} | undefined;
|
|
@@ -6,7 +6,7 @@ import { AppLink } from "./AppLink.js";
|
|
|
6
6
|
import { IconButton } from "./IconButton.js";
|
|
7
7
|
import { formatInteger } from "../format.js";
|
|
8
8
|
import { Select } from "./Select.js";
|
|
9
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
//#region src/components/Pagination.tsx
|
|
11
11
|
/** Page sizes a list offers; the first is the default. */
|
|
12
12
|
var PAGE_SIZES = [
|
|
@@ -45,6 +45,7 @@ function pageSteps(page, count, max = 7) {
|
|
|
45
45
|
* nothing when everything fits on one page. */
|
|
46
46
|
function Pagination({ page, pageSize, total, hasNext, onPageChange, pageHref, pageSizes = PAGE_SIZES, onPageSizeChange, summary, pageNumbers = false, className }) {
|
|
47
47
|
const t = useStrings().list;
|
|
48
|
+
const locale = useLocale();
|
|
48
49
|
const start = page * pageSize + 1;
|
|
49
50
|
const pageEnd = (page + 1) * pageSize;
|
|
50
51
|
const known = total !== void 0;
|
|
@@ -59,7 +60,7 @@ function Pagination({ page, pageSize, total, hasNext, onPageChange, pageHref, pa
|
|
|
59
60
|
const position = summary ?? (known ? t.pager(start, end, pastCap ? end : total, pastCap) : null);
|
|
60
61
|
return /* @__PURE__ */ jsxs("nav", {
|
|
61
62
|
"aria-label": t.pagination,
|
|
62
|
-
className: cx("flex h-8 shrink-0 items-center justify-between gap-3 px-2.5", className),
|
|
63
|
+
className: cx("@container/pager flex h-8 shrink-0 items-center justify-between gap-3 px-2.5", className),
|
|
63
64
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
64
65
|
className: "flex min-w-0 flex-1 items-center gap-3 text-xs text-(--theme-muted-text-color)",
|
|
65
66
|
children: [position && /* @__PURE__ */ jsx("span", {
|
|
@@ -87,22 +88,39 @@ function Pagination({ page, pageSize, total, hasNext, onPageChange, pageHref, pa
|
|
|
87
88
|
onPageChange,
|
|
88
89
|
pageHref
|
|
89
90
|
}),
|
|
90
|
-
pageNumbers && pageCount !== void 0 && pageCount > 1 && /* @__PURE__ */
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
pageNumbers && pageCount !== void 0 && pageCount > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
92
|
+
/* @__PURE__ */ jsx(PageNumbers, {
|
|
93
|
+
steps: pageSteps(page, pageCount, typeof pageNumbers === "object" ? pageNumbers.max : void 0),
|
|
94
|
+
page,
|
|
95
|
+
className: "hidden @min-[34rem]/pager:flex",
|
|
96
|
+
onPageChange,
|
|
97
|
+
pageHref
|
|
98
|
+
}),
|
|
99
|
+
/* @__PURE__ */ jsx(PageNumbers, {
|
|
100
|
+
steps: pageSteps(page, pageCount, 5),
|
|
101
|
+
page,
|
|
102
|
+
className: "hidden @min-[19rem]/pager:flex @min-[34rem]/pager:hidden",
|
|
103
|
+
onPageChange,
|
|
104
|
+
pageHref
|
|
105
|
+
}),
|
|
106
|
+
/* @__PURE__ */ jsxs("span", {
|
|
107
|
+
className: "flex items-center px-1 text-xs whitespace-nowrap text-(--theme-muted-text-color) tabular-nums @min-[19rem]/pager:hidden",
|
|
108
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
109
|
+
className: "sr-only",
|
|
110
|
+
children: t.pageOfTotal(page + 1, pageCount)
|
|
111
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
95
112
|
"aria-hidden": "true",
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
children: [
|
|
114
|
+
/* @__PURE__ */ jsx("span", {
|
|
115
|
+
className: "text-(--theme-app)",
|
|
116
|
+
children: formatInteger(page + 1, locale)
|
|
117
|
+
}),
|
|
118
|
+
" / ",
|
|
119
|
+
formatInteger(pageCount, locale)
|
|
120
|
+
]
|
|
121
|
+
})]
|
|
122
|
+
})
|
|
123
|
+
] }),
|
|
106
124
|
/* @__PURE__ */ jsx(PageStep, {
|
|
107
125
|
to: page + 1,
|
|
108
126
|
enabled: hasMore,
|
|
@@ -115,6 +133,25 @@ function Pagination({ page, pageSize, total, hasNext, onPageChange, pageHref, pa
|
|
|
115
133
|
})]
|
|
116
134
|
});
|
|
117
135
|
}
|
|
136
|
+
function PageNumbers({ steps, page, className, onPageChange, pageHref }) {
|
|
137
|
+
const t = useStrings().list;
|
|
138
|
+
return /* @__PURE__ */ jsx("ol", {
|
|
139
|
+
className: cx("m-0 list-none items-center gap-1 p-0", className),
|
|
140
|
+
children: steps.map((step, i) => /* @__PURE__ */ jsx("li", {
|
|
141
|
+
className: "flex",
|
|
142
|
+
children: step === null ? /* @__PURE__ */ jsx(MoreIcon, {
|
|
143
|
+
"aria-hidden": "true",
|
|
144
|
+
className: "size-3.5 w-4 text-(--theme-muted-text-color)"
|
|
145
|
+
}) : /* @__PURE__ */ jsx(PageNumber, {
|
|
146
|
+
to: step,
|
|
147
|
+
current: step === page,
|
|
148
|
+
label: t.page(step + 1),
|
|
149
|
+
onPageChange,
|
|
150
|
+
pageHref
|
|
151
|
+
})
|
|
152
|
+
}, step ?? `gap-${i}`))
|
|
153
|
+
});
|
|
154
|
+
}
|
|
118
155
|
function PageStep({ to, enabled, label, icon, onPageChange, pageHref }) {
|
|
119
156
|
if (pageHref && enabled) return /* @__PURE__ */ jsx(IconButton, {
|
|
120
157
|
variant: "outline",
|
|
@@ -588,6 +588,8 @@ export interface UIStrings {
|
|
|
588
588
|
chart: {
|
|
589
589
|
/** The row under a chart's tooltip that sums its series. */
|
|
590
590
|
total: string;
|
|
591
|
+
/** The series that sums a chart's series past the eighth. */
|
|
592
|
+
other: string;
|
|
591
593
|
};
|
|
592
594
|
list: {
|
|
593
595
|
moreActions: string;
|
|
@@ -635,6 +637,8 @@ export interface UIStrings {
|
|
|
635
637
|
pagination: string;
|
|
636
638
|
/** A numbered page's name in `Pagination`, counting from 1. */
|
|
637
639
|
page: (page: number) => string;
|
|
640
|
+
/** Where the page sits, for `Pagination` too narrow for page numbers: it shows "5 / 12". */
|
|
641
|
+
pageOfTotal: (page: number, total: number) => string;
|
|
638
642
|
removeSearchTerm: (term: string) => string;
|
|
639
643
|
};
|
|
640
644
|
/** The chip row of a list's active filters, its Filter menu and its pickers. Field and option
|
|
@@ -580,7 +580,10 @@ var DEFAULTS = {
|
|
|
580
580
|
backspace: "Backspace",
|
|
581
581
|
delete: "Delete"
|
|
582
582
|
},
|
|
583
|
-
chart: {
|
|
583
|
+
chart: {
|
|
584
|
+
total: "Total",
|
|
585
|
+
other: "Other"
|
|
586
|
+
},
|
|
584
587
|
list: {
|
|
585
588
|
moreActions: "More actions",
|
|
586
589
|
labelName: "name",
|
|
@@ -619,6 +622,7 @@ var DEFAULTS = {
|
|
|
619
622
|
paginationNext: "Next",
|
|
620
623
|
pagination: "Pagination",
|
|
621
624
|
page: (page) => `Page ${page}`,
|
|
625
|
+
pageOfTotal: (page, total) => `Page ${page} of ${total}`,
|
|
622
626
|
removeSearchTerm: (term) => `Remove filter ${term}`
|
|
623
627
|
},
|
|
624
628
|
filters: {
|
|
@@ -13,6 +13,9 @@ export interface CellPlacement {
|
|
|
13
13
|
after?: React.ReactNode;
|
|
14
14
|
stacked?: boolean | undefined;
|
|
15
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";
|
|
16
19
|
/** Set by a list row around each of its cells; a `Table.Item` takes what it asks. */
|
|
17
20
|
export declare const CellPlacementContext: React.Context<CellPlacement | null>;
|
|
18
21
|
interface ItemProps extends React.TdHTMLAttributes<HTMLTableCellElement> {
|
package/dist/components/Table.js
CHANGED
|
@@ -23,19 +23,22 @@ 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";
|
|
26
29
|
/** Set by a list row around each of its cells; a `Table.Item` takes what it asks. */
|
|
27
30
|
var CellPlacementContext = createContext(null);
|
|
28
31
|
function Item({ wrap = false, children, className, ...tdProps }) {
|
|
29
32
|
const density = useContext(DensityContext);
|
|
30
33
|
const placed = useContext(CellPlacementContext);
|
|
31
34
|
if (placed?.stacked) return React.Children.toArray(children).length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
32
|
-
className:
|
|
35
|
+
className: STACKED_ITEM,
|
|
33
36
|
"data-stacked": true,
|
|
34
37
|
children
|
|
35
38
|
}) : null;
|
|
36
39
|
return /* @__PURE__ */ jsx("td", {
|
|
37
40
|
...tdProps,
|
|
38
|
-
className: cx(cellX, cellY[density], wrap ? wrapY[density] : "whitespace-nowrap", className, placed?.className),
|
|
41
|
+
className: cx(cellX, cellY[density], wrap ? wrapY[density] : "overflow-clip text-ellipsis whitespace-nowrap [overflow-clip-margin:4px]", className, placed?.className),
|
|
39
42
|
children: /* @__PURE__ */ jsxs(CellPlacementContext, {
|
|
40
43
|
value: null,
|
|
41
44
|
children: [children, placed?.after]
|
|
@@ -118,4 +121,4 @@ function Table({ density = "comfortable", columns, caps = true, flush = false, c
|
|
|
118
121
|
Table.Header = Header;
|
|
119
122
|
Table.Item = Item;
|
|
120
123
|
//#endregion
|
|
121
|
-
export { CellPlacementContext, Table };
|
|
124
|
+
export { CellPlacementContext, STACKED_ITEM, Table };
|
package/dist/components/Tabs.js
CHANGED
|
@@ -2,8 +2,9 @@ import { cx } from "../cx.js";
|
|
|
2
2
|
import { useStickyTop } from "./stickyTop.js";
|
|
3
3
|
import { Tooltip } from "./Tooltip.js";
|
|
4
4
|
import { AppLink } from "./AppLink.js";
|
|
5
|
+
import { fadeMask, useHiddenEnds } from "./scrollFade.js";
|
|
5
6
|
import { SegmentContent, segmentClassName, segmentGroupClassName } from "./segmented.js";
|
|
6
|
-
import { useState } from "react";
|
|
7
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
7
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
9
|
import { Tabs } from "@base-ui/react/tabs";
|
|
9
10
|
//#region src/components/Tabs.tsx
|
|
@@ -26,6 +27,31 @@ function Tabs$1({ label, items, value, defaultValue, onValueChange, variant = "l
|
|
|
26
27
|
const showRow = items.length > 1 || !!end;
|
|
27
28
|
const [row, setRow] = useState(null);
|
|
28
29
|
useStickyTop(row, !!sticky);
|
|
30
|
+
const scroller = useHiddenEnds(!segmented && showRow);
|
|
31
|
+
const scrolled = useRef(false);
|
|
32
|
+
useLayoutEffect(() => {
|
|
33
|
+
const list = scroller.ref.current;
|
|
34
|
+
const tab = list?.querySelector("[aria-selected=\"true\"]");
|
|
35
|
+
if (!list || !tab || segmented) return;
|
|
36
|
+
const margin = 24;
|
|
37
|
+
const left = tab.offsetLeft;
|
|
38
|
+
const right = left + tab.offsetWidth;
|
|
39
|
+
let to;
|
|
40
|
+
if (left - margin < list.scrollLeft) to = Math.max(0, left - margin);
|
|
41
|
+
else if (right + margin > list.scrollLeft + list.clientWidth) to = right + margin - list.clientWidth;
|
|
42
|
+
if (to !== void 0) {
|
|
43
|
+
const smooth = scrolled.current && !globalThis.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
44
|
+
list.scrollTo?.({
|
|
45
|
+
left: to,
|
|
46
|
+
behavior: smooth ? "smooth" : "instant"
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
scrolled.current = true;
|
|
50
|
+
}, [
|
|
51
|
+
current,
|
|
52
|
+
segmented,
|
|
53
|
+
scroller.ref
|
|
54
|
+
]);
|
|
29
55
|
const tabs = items.map((item) => {
|
|
30
56
|
const tab = /* @__PURE__ */ jsx(Tabs.Tab, {
|
|
31
57
|
value: item.value,
|
|
@@ -61,7 +87,10 @@ function Tabs$1({ label, items, value, defaultValue, onValueChange, variant = "l
|
|
|
61
87
|
className: cx("flex items-center justify-between gap-2", !segmented && "border-b border-(--theme-border)", sticky && "sticky top-0 z-(--layer-sticky) bg-(--theme-app-bg)", className),
|
|
62
88
|
children: [/* @__PURE__ */ jsxs(Tabs.List, {
|
|
63
89
|
"aria-label": label,
|
|
64
|
-
|
|
90
|
+
ref: segmented ? void 0 : scroller.ref,
|
|
91
|
+
onScroll: segmented ? void 0 : scroller.onScroll,
|
|
92
|
+
style: segmented ? void 0 : fadeMask(scroller.ends),
|
|
93
|
+
className: cx(segmented ? segmentGroupClassName(layout) : "relative flex min-w-0 overflow-x-auto rtl:[--fade-end:left]"),
|
|
65
94
|
children: [tabs, !segmented && /* @__PURE__ */ jsx(Tabs.Indicator, { className: "absolute bottom-0 left-0 h-0.5 w-(--active-tab-width) translate-x-(--active-tab-left) bg-(--theme-link) transition-[translate,width] duration-200 ease-in-out motion-reduce:transition-none" })]
|
|
66
95
|
}), end]
|
|
67
96
|
}), children]
|
package/dist/components/Tag.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ export interface TagStyle {
|
|
|
6
6
|
caps?: boolean | undefined;
|
|
7
7
|
}
|
|
8
8
|
/** The one tag look: a pill with a hairline border and 11px text. Another component's chip,
|
|
9
|
-
* such as a picker's chosen value, uses it to look the same.
|
|
9
|
+
* such as a picker's chosen value, uses it to look the same. It has the panel's fill, so its
|
|
10
|
+
* edge shows on a hovered or selected row too, whose fill can match the border. */
|
|
10
11
|
export declare function tagClasses({ mono, caps }?: TagStyle): string;
|
|
11
12
|
/** Any other attribute and the ref reach the tag's element, its link with `href` and no `onRemove`,
|
|
12
13
|
* so it can be a Tooltip's or a popover's direct child. */
|
package/dist/components/Tag.js
CHANGED
|
@@ -7,9 +7,10 @@ import { useLayoutEffect, useRef, useState } from "react";
|
|
|
7
7
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/components/Tag.tsx
|
|
9
9
|
/** The one tag look: a pill with a hairline border and 11px text. Another component's chip,
|
|
10
|
-
* such as a picker's chosen value, uses it to look the same.
|
|
10
|
+
* such as a picker's chosen value, uses it to look the same. It has the panel's fill, so its
|
|
11
|
+
* edge shows on a hovered or selected row too, whose fill can match the border. */
|
|
11
12
|
function tagClasses({ mono = false, caps = false } = {}) {
|
|
12
|
-
return cx("inline-flex h-5 max-w-full min-w-0 shrink-0 items-center gap-1 rounded-full border border-(--theme-border) px-2 text-[11px] leading-none whitespace-nowrap text-(--theme-app)", mono ? "font-mono" : "font-sans", caps && "text-[10px] font-medium tracking-wide uppercase");
|
|
13
|
+
return cx("inline-flex h-5 max-w-full min-w-0 shrink-0 items-center gap-1 rounded-full border border-(--theme-border) bg-(--theme-panel-bg) px-2 text-[11px] leading-none whitespace-nowrap text-(--theme-app)", mono ? "font-mono" : "font-sans", caps && "text-[10px] font-medium tracking-wide uppercase");
|
|
13
14
|
}
|
|
14
15
|
/** A tag or label on a thing: a pill with an optional color dot, a link with `href`, and an ×
|
|
15
16
|
* with `onRemove`. */
|
|
@@ -27,7 +28,7 @@ function Tag({ children, color, href, onRemove, removeLabel, title, mono, caps,
|
|
|
27
28
|
mono,
|
|
28
29
|
caps
|
|
29
30
|
}), className);
|
|
30
|
-
const hover = "transition-colors hover:bg-(--theme-
|
|
31
|
+
const hover = "transition-colors hover:bg-(--theme-muted-panel-bg)";
|
|
31
32
|
if (!onRemove) return href ? /* @__PURE__ */ jsx(AppLink, {
|
|
32
33
|
...rest,
|
|
33
34
|
ref,
|
|
@@ -173,7 +174,7 @@ function MoreTags({ tags, ...style }) {
|
|
|
173
174
|
trigger: /* @__PURE__ */ jsxs("button", {
|
|
174
175
|
type: "button",
|
|
175
176
|
"aria-label": `${common.moreCount(tags.length)}: ${names.join(", ")}`,
|
|
176
|
-
className: cx(tagClasses(style), "cursor-pointer text-(--theme-muted-text-color) tabular-nums hover:bg-(--theme-
|
|
177
|
+
className: cx(tagClasses(style), "cursor-pointer text-(--theme-muted-text-color) tabular-nums hover:bg-(--theme-muted-panel-bg) hover:text-(--theme-app) focus-visible:outline-2 focus-visible:outline-(--theme-focus) data-popup-open:bg-(--theme-muted-panel-bg)"),
|
|
177
178
|
children: ["+", count]
|
|
178
179
|
}),
|
|
179
180
|
children: /* @__PURE__ */ jsx("ul", {
|
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export declare
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
2
|
+
/** How many series a chart colors: a series past the eighth folds into "Other" (`foldSeries`)
|
|
3
|
+
* rather than taking a hue made up for it. */
|
|
4
|
+
export declare const SERIES_LIMIT: 8;
|
|
5
|
+
/** The key `foldSeries` gives the series that sums those past the limit. */
|
|
6
|
+
export declare const OTHER_SERIES_KEY = "__other__";
|
|
7
|
+
/** The mark color of the "Other" series: a neutral gray, so it never reads as one more series. */
|
|
8
|
+
export declare const OTHER_SERIES_COLOR = "color-mix(in oklch, var(--theme-muted-text-color) 75%, var(--theme-panel-bg))";
|
|
9
|
+
/** The color of a chart's nth series, 1–8: the theme's category hue, between the slot's soft
|
|
10
|
+
* fill and its text and twice as saturated, so a bar or a line stands out from the page in
|
|
11
|
+
* light and dark alike. Past the eighth it is the "Other" gray; a hue is never repeated. */
|
|
12
|
+
export declare function seriesSlotColor(position: number): string;
|
|
13
|
+
/** Colors for a chart's series by their place in `keys`: the first takes the first color, the
|
|
14
|
+
* second the next, so the first few are always the most distinct. Pass every key the chart can
|
|
15
|
+
* show, in an order that doesn't depend on the data shown, such as by name or as configured:
|
|
16
|
+
* a filter that hides a series then leaves the others their colors. `OTHER_SERIES_KEY`, and any
|
|
17
|
+
* key past the eighth, is the "Other" gray; fold them first with `foldSeries`. */
|
|
11
18
|
export declare function seriesColors(keys: readonly string[]): Record<string, string>;
|
|
19
|
+
/** Splits a chart's series at the limit: up to eight show as they are, and more keep the first
|
|
20
|
+
* seven and fold the rest into one "Other" series, `OTHER_SERIES_KEY`, whose value at a point
|
|
21
|
+
* is the sum of `folded`. Name it with the `chart.other` string. */
|
|
22
|
+
export declare function foldSeries(keys: readonly string[], limit?: number): {
|
|
23
|
+
keys: string[];
|
|
24
|
+
folded: string[];
|
|
25
|
+
};
|
|
12
26
|
export type SeriesSwatchShape = 'square' | 'dot' | 'line';
|
|
13
27
|
/** A series' color as a mark, for a legend or a tooltip's row. Only seen: the series' name
|
|
14
28
|
* beside it says which it is. */
|
|
@@ -64,6 +78,39 @@ export declare function ChartTooltipContent({ label, items, formatValue, total,
|
|
|
64
78
|
/** Lists the largest value first; on by default. */
|
|
65
79
|
sort?: boolean | undefined;
|
|
66
80
|
className?: string | undefined;
|
|
67
|
-
/** Places it
|
|
81
|
+
/** Places it; `ChartTooltipBeside` places it beside a crosshair. */
|
|
68
82
|
style?: CSSProperties | undefined;
|
|
69
83
|
}): import("react").JSX.Element;
|
|
84
|
+
export interface ChartTooltipPlacement {
|
|
85
|
+
/** The tooltip's left edge. */
|
|
86
|
+
left: number;
|
|
87
|
+
side: 'left' | 'right';
|
|
88
|
+
/** A width to hold it to when neither side has room for all of it, its names truncating. */
|
|
89
|
+
maxWidth: number | null;
|
|
90
|
+
}
|
|
91
|
+
/** Where a chart's tooltip goes beside a crosshair at `x`, so it never covers the column under
|
|
92
|
+
* the pointer: `gap` to the `prefer` side, or the other when it doesn't fit between `x` and
|
|
93
|
+
* that edge, or held to the wider side when neither fits. `start` and `end` bound it, in the
|
|
94
|
+
* same coordinates as `x`, such as the chart's box or the viewport. A charting library's
|
|
95
|
+
* tooltip takes `left` as its position. */
|
|
96
|
+
export declare function chartTooltipPlacement({ x, width, start, end, gap, prefer, }: {
|
|
97
|
+
x: number;
|
|
98
|
+
width: number;
|
|
99
|
+
start: number;
|
|
100
|
+
end: number;
|
|
101
|
+
gap?: number | undefined;
|
|
102
|
+
prefer?: 'left' | 'right' | undefined;
|
|
103
|
+
}): ChartTooltipPlacement;
|
|
104
|
+
/** Holds a chart's tooltip beside its crosshair, inside a positioned box around the plot: `x`
|
|
105
|
+
* is the crosshair in that box, and the tooltip sits `top` from its top, to the crosshair's
|
|
106
|
+
* inline end, or its start where the box has no room. When neither side of the box has room,
|
|
107
|
+
* as on a phone, it may reach past the box to the viewport's edge, and is held narrower when
|
|
108
|
+
* even that is too small. It never covers the hovered column or the pointer on it. Nothing
|
|
109
|
+
* shows while `x` is null. */
|
|
110
|
+
export declare function ChartTooltipBeside({ x, top, gap, className, children, }: {
|
|
111
|
+
x: number | null;
|
|
112
|
+
top?: number | undefined;
|
|
113
|
+
gap?: number | undefined;
|
|
114
|
+
className?: string | undefined;
|
|
115
|
+
children: ReactNode;
|
|
116
|
+
}): import("react").JSX.Element | null;
|