@parallelworks/ui 0.89.0 → 0.91.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 +2 -1
- package/dist/ai/components/AllocationSelector.js +3 -2
- package/dist/ai/strings.d.ts +1 -0
- package/dist/ai/strings.js +1 -0
- package/dist/components/Provider.d.ts +13 -0
- package/dist/components/Provider.js +11 -0
- package/dist/components/RunStatusIcon.d.ts +4 -4
- package/dist/components/RunStatusIcon.js +4 -4
- package/dist/components/StatusBadge.d.ts +12 -8
- package/dist/components/StatusBadge.js +13 -19
- package/dist/components/StatusLabel.d.ts +44 -0
- package/dist/components/StatusLabel.js +92 -0
- package/dist/components/Tag.d.ts +7 -2
- package/dist/components/Tag.js +7 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/list/ListFilterBar.d.ts +6 -1
- package/dist/list/ListFilterBar.js +126 -8
- package/dist/list/ListPage.d.ts +23 -2
- package/dist/list/ListPage.js +38 -15
- package/dist/list/ListToolbar.d.ts +13 -7
- package/dist/list/ListToolbar.js +28 -17
- package/dist/list/index.d.ts +1 -1
- package/dist/list/index.js +3 -3
- package/dist/list/listSearch.d.ts +18 -2
- package/dist/list/listSearch.js +24 -8
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ import { Calendar } from "../components/DatePicker.js";
|
|
|
11
11
|
import { Drawer } from "../components/Drawer.js";
|
|
12
12
|
import { ToggleGroup } from "../components/ToggleGroup.js";
|
|
13
13
|
import { DATE_PRESETS, fieldOperators, isDatePreset, isMultiple, operatorCount, parseDay, toDay } from "./filterModel.js";
|
|
14
|
+
import { ListToolbarInlineContext } from "./listSearch.js";
|
|
14
15
|
import { Menu } from "@base-ui/react/menu";
|
|
15
16
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
16
17
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -155,6 +156,7 @@ function FilterChip({ filters, filter, field, stretch = false, answers, onRemove
|
|
|
155
156
|
}, i))
|
|
156
157
|
}),
|
|
157
158
|
/* @__PURE__ */ jsx("span", {
|
|
159
|
+
"data-filter-value": "",
|
|
158
160
|
className: cx("min-w-0 truncate", !stretch && "max-w-40", unset && "text-(--theme-muted-text-color)"),
|
|
159
161
|
children: valueText
|
|
160
162
|
}),
|
|
@@ -732,7 +734,7 @@ function QuickRow({ filters, groups, narrow, onMore, end, stacked = false }) {
|
|
|
732
734
|
const tops = /* @__PURE__ */ new Set();
|
|
733
735
|
for (const button of el.querySelectorAll("button")) tops.add(Math.round(button.getBoundingClientRect().top));
|
|
734
736
|
const shown = limit ?? total;
|
|
735
|
-
if (tops.size > 1 && shown >
|
|
737
|
+
if (tops.size > 1 && shown > 0) setLimit(shown - 1);
|
|
736
738
|
});
|
|
737
739
|
const cap = fits && limit !== null ? limit : total;
|
|
738
740
|
let index = 0;
|
|
@@ -802,7 +804,95 @@ function QuickRule() {
|
|
|
802
804
|
* The list's quick filters show above the chips as toggles, always, setting the same filters
|
|
803
805
|
* the chips edit. At phone width each keeps to one line, and "+n more" opens the rest in the
|
|
804
806
|
* same sheet. */
|
|
805
|
-
function ListFilterBar({
|
|
807
|
+
function ListFilterBar({ end, trailing, ...props }) {
|
|
808
|
+
if (end === void 0 || end === null || end === false) return /* @__PURE__ */ jsx(FilterRow, {
|
|
809
|
+
trailing,
|
|
810
|
+
...props
|
|
811
|
+
});
|
|
812
|
+
return /* @__PURE__ */ jsx(SharedRow, {
|
|
813
|
+
end,
|
|
814
|
+
trailing,
|
|
815
|
+
...props
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
var VALUE_MIN_PX = 88;
|
|
819
|
+
/** The chips with the list's controls at their first line's end. */
|
|
820
|
+
function SharedRow({ end, trailing, ...props }) {
|
|
821
|
+
const box = useRef(null);
|
|
822
|
+
const controls = useRef(null);
|
|
823
|
+
const endBox = useRef(null);
|
|
824
|
+
const [width, setWidth] = useState(0);
|
|
825
|
+
const [trailingLast, setTrailingLast] = useState(false);
|
|
826
|
+
const [reach, setReach] = useState(0);
|
|
827
|
+
const [noReach, setNoReach] = useState(false);
|
|
828
|
+
const chipKeys = props.filters?.chips.map((c) => c.field).join("\0") ?? "";
|
|
829
|
+
const shows = props.filters !== void 0 && rowShows(props.filters);
|
|
830
|
+
useLayoutEffect(() => {
|
|
831
|
+
const el = box.current;
|
|
832
|
+
if (!el) return;
|
|
833
|
+
const measure = () => setWidth(el.getBoundingClientRect().width);
|
|
834
|
+
measure();
|
|
835
|
+
const observer = new ResizeObserver(measure);
|
|
836
|
+
observer.observe(el);
|
|
837
|
+
return () => observer.disconnect();
|
|
838
|
+
}, []);
|
|
839
|
+
useLayoutEffect(() => {
|
|
840
|
+
setTrailingLast(false);
|
|
841
|
+
setNoReach(false);
|
|
842
|
+
}, [width, chipKeys]);
|
|
843
|
+
useLayoutEffect(() => {
|
|
844
|
+
const el = box.current;
|
|
845
|
+
if (!el || !trailing || !shows) return;
|
|
846
|
+
const chips = Array.from(el.querySelectorAll("[data-filter-chip]"));
|
|
847
|
+
const firstTop = chips[0]?.offsetTop;
|
|
848
|
+
if (trailingLast) {
|
|
849
|
+
const moved = el.querySelector("[data-filter-trailing]");
|
|
850
|
+
if (!noReach && moved && moved.offsetTop === firstTop) {
|
|
851
|
+
setNoReach(true);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const next = noReach ? 0 : (controls.current?.getBoundingClientRect().width ?? 0) + GAP_PX;
|
|
855
|
+
if (Math.abs(next - reach) > .5) setReach(next);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
if (chips.some((chip) => {
|
|
859
|
+
const value = chip.querySelector("[data-filter-value]");
|
|
860
|
+
return chip.offsetTop === firstTop && value !== null && value.clientWidth < Math.min(value.scrollWidth, VALUE_MIN_PX);
|
|
861
|
+
})) {
|
|
862
|
+
setReach((endBox.current?.getBoundingClientRect().width ?? 0) + GAP_PX);
|
|
863
|
+
setTrailingLast(true);
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
return /* @__PURE__ */ jsx(ListToolbarInlineContext, {
|
|
867
|
+
value: true,
|
|
868
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
869
|
+
ref: box,
|
|
870
|
+
className: "flex min-h-10 items-start gap-3",
|
|
871
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
872
|
+
className: "min-w-0 flex-1 empty:hidden",
|
|
873
|
+
children: /* @__PURE__ */ jsx(FilterRow, {
|
|
874
|
+
...props,
|
|
875
|
+
trailing: trailingLast ? trailing : void 0,
|
|
876
|
+
trailingReach: trailingLast ? reach : 0
|
|
877
|
+
})
|
|
878
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
879
|
+
ref: controls,
|
|
880
|
+
className: "ml-auto flex min-h-10 shrink-0 items-center gap-1.5",
|
|
881
|
+
children: [shows && !trailingLast && trailing, /* @__PURE__ */ jsx("div", {
|
|
882
|
+
ref: endBox,
|
|
883
|
+
className: "flex items-center",
|
|
884
|
+
children: end
|
|
885
|
+
})]
|
|
886
|
+
})]
|
|
887
|
+
})
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
var GAP_PX = 12;
|
|
891
|
+
/** Whether the row has anything to show: a chip, or a quick filter's toggles. */
|
|
892
|
+
function rowShows(filters) {
|
|
893
|
+
return orderedChips(filters).length > 0 || (filters.quick ?? []).some((q) => filters.fields.some((f) => f.key === q.field));
|
|
894
|
+
}
|
|
895
|
+
function FilterRow({ filters, trailing, trailingReach = 0, label, className }) {
|
|
806
896
|
const strings = useStrings();
|
|
807
897
|
const t = strings.filters;
|
|
808
898
|
const registerBar = filters?.registerBar;
|
|
@@ -812,6 +902,8 @@ function ListFilterBar({ filters, trailing, label, className }) {
|
|
|
812
902
|
const [narrow, setNarrow] = useState(false);
|
|
813
903
|
const [width, setWidth] = useState(0);
|
|
814
904
|
const [limit, setLimit] = useState(null);
|
|
905
|
+
const [clearFolded, setClearFolded] = useState(false);
|
|
906
|
+
const [addFolded, setAddFolded] = useState(false);
|
|
815
907
|
const [sheet, setSheet] = useState(false);
|
|
816
908
|
const [focusAt, setFocusAt] = useState(null);
|
|
817
909
|
const chips = filters ? orderedChips(filters) : [];
|
|
@@ -826,6 +918,7 @@ function ListFilterBar({ filters, trailing, label, className }) {
|
|
|
826
918
|
const empty = chips.length === 0;
|
|
827
919
|
const hasQuick = quick.length > 0;
|
|
828
920
|
const chipKeys = chips.map((c) => c.filter.field).join("\0");
|
|
921
|
+
const fixedChips = chips.filter((c) => c.field.required || c.field.pinned).length;
|
|
829
922
|
useLayoutEffect(() => {
|
|
830
923
|
const el = box.current;
|
|
831
924
|
if (!el) return;
|
|
@@ -839,7 +932,11 @@ function ListFilterBar({ filters, trailing, label, className }) {
|
|
|
839
932
|
observer.observe(el);
|
|
840
933
|
return () => observer.disconnect();
|
|
841
934
|
}, [empty, hasQuick]);
|
|
842
|
-
useLayoutEffect(() =>
|
|
935
|
+
useLayoutEffect(() => {
|
|
936
|
+
setLimit(null);
|
|
937
|
+
setClearFolded(false);
|
|
938
|
+
setAddFolded(false);
|
|
939
|
+
}, [
|
|
843
940
|
width,
|
|
844
941
|
chipKeys,
|
|
845
942
|
narrow
|
|
@@ -848,9 +945,27 @@ function ListFilterBar({ filters, trailing, label, className }) {
|
|
|
848
945
|
const el = row.current;
|
|
849
946
|
if (!el || !narrow) return;
|
|
850
947
|
const tops = /* @__PURE__ */ new Set();
|
|
851
|
-
for (const child of el.children) tops.add(Math.round(child.offsetTop));
|
|
948
|
+
for (const child of el.children) if (!child.hasAttribute("data-filter-clear")) tops.add(Math.round(child.offsetTop));
|
|
852
949
|
const shown = limit ?? chips.length;
|
|
853
|
-
if (tops.size > 2 && shown >
|
|
950
|
+
if (tops.size > 2 && shown > Math.max(fixedChips, 1)) {
|
|
951
|
+
setLimit(shown - 1);
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
const add = el.querySelector("[data-filter-add]");
|
|
955
|
+
const addItem = add ? Array.from(el.children).find((child) => child.contains(add)) : void 0;
|
|
956
|
+
const before = addItem?.previousElementSibling;
|
|
957
|
+
if (addItem instanceof HTMLElement && before instanceof HTMLElement && Math.round(addItem.offsetTop) > Math.round(before.offsetTop)) {
|
|
958
|
+
if (chips.length > shown) {
|
|
959
|
+
setAddFolded(true);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
if (shown > fixedChips) {
|
|
963
|
+
setLimit(shown - 1);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
const clear = el.querySelector("[data-filter-clear]");
|
|
968
|
+
if (add && clear && Math.round(clear.offsetTop) > Math.round(add.offsetTop)) setClearFolded(true);
|
|
854
969
|
});
|
|
855
970
|
useEffect(() => {
|
|
856
971
|
if (focusAt === null || !row.current) return;
|
|
@@ -858,7 +973,7 @@ function ListFilterBar({ filters, trailing, label, className }) {
|
|
|
858
973
|
setFocusAt(null);
|
|
859
974
|
}, [focusAt]);
|
|
860
975
|
const visible = narrow && limit !== null ? limit : chips.length;
|
|
861
|
-
const shownChips = chips.filter((c, i) => i < visible || c.filter.field === filters?.draft);
|
|
976
|
+
const shownChips = chips.filter((c, i) => i < visible || c.field.required || c.field.pinned || c.filter.field === filters?.draft);
|
|
862
977
|
const overflow = chips.length - shownChips.length;
|
|
863
978
|
const request = filters?.editRequest;
|
|
864
979
|
const hiddenRequest = request && !shownChips.some((c) => c.filter.field === request.field) && chips.some((c) => c.filter.field === request.field);
|
|
@@ -870,6 +985,7 @@ function ListFilterBar({ filters, trailing, label, className }) {
|
|
|
870
985
|
const resetting = filters.hasDefaults;
|
|
871
986
|
const clearButton = (resetting ? !filters.isDefault : removable) && /* @__PURE__ */ jsx("button", {
|
|
872
987
|
type: "button",
|
|
988
|
+
"data-filter-clear": "",
|
|
873
989
|
onClick: resetting ? filters.reset : filters.clear,
|
|
874
990
|
className: buttonClasses({
|
|
875
991
|
variant: "ghost",
|
|
@@ -938,10 +1054,12 @@ function ListFilterBar({ filters, trailing, label, className }) {
|
|
|
938
1054
|
className: "flex h-7 cursor-pointer items-center rounded-md border border-dashed border-(--theme-border) px-2 text-[12px] text-(--theme-app) hover:bg-(--theme-hover) focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--theme-focus)",
|
|
939
1055
|
children: t.overflow(overflow)
|
|
940
1056
|
}),
|
|
941
|
-
addButton(narrow),
|
|
942
|
-
clearButton,
|
|
1057
|
+
!(narrow && addFolded) && addButton(narrow),
|
|
1058
|
+
!(narrow && (clearFolded || addFolded)) && clearButton,
|
|
943
1059
|
trailing && /* @__PURE__ */ jsx("div", {
|
|
1060
|
+
"data-filter-trailing": "",
|
|
944
1061
|
className: "ml-auto flex items-center gap-1.5",
|
|
1062
|
+
style: trailingReach ? { marginRight: -trailingReach } : void 0,
|
|
945
1063
|
children: trailing
|
|
946
1064
|
}),
|
|
947
1065
|
!hasQuick && drawer
|
package/dist/list/ListPage.d.ts
CHANGED
|
@@ -14,7 +14,10 @@ export interface ListPageProps {
|
|
|
14
14
|
bleed?: boolean | undefined;
|
|
15
15
|
/** The list's search, filters and view controls, above the rows, such as a
|
|
16
16
|
* `ListToolbar`. The page's trail and actions, "+ Add" among them, go in
|
|
17
|
-
* its `PageHeader`. With `total`, it hides while the list is empty.
|
|
17
|
+
* its `PageHeader`. With `total`, it hides while the list is empty. Once
|
|
18
|
+
* the page shows the count under its rows, the toolbar sits at the end of
|
|
19
|
+
* the filter chips' first line, or takes the line alone while no chip
|
|
20
|
+
* shows. */
|
|
18
21
|
toolbar?: ReactNode;
|
|
19
22
|
/** Under the toolbar: the chips of the list's active filters. By default the view's own
|
|
20
23
|
* `ListFilterBar`, shown while a filter is on; pass one to add a trailing slot, such as Save
|
|
@@ -43,8 +46,26 @@ export interface ListPageProps {
|
|
|
43
46
|
/** Keeps the toolbar while the list is empty, for one whose controls still
|
|
44
47
|
* matter then, such as a filter set by default that hides everything. */
|
|
45
48
|
toolbarWhenEmpty?: boolean | undefined;
|
|
49
|
+
/** Names what the list counts. With `total` (and `matches`), the page
|
|
50
|
+
* shows the count under its last row: "7 tokens", or "2 of 7 tokens"
|
|
51
|
+
* while a search or filter narrows the list. It shows only while there
|
|
52
|
+
* are rows: the empty and no-matches states say enough. A `ListToolbar`'s
|
|
53
|
+
* own `count` shows there too. */
|
|
54
|
+
count?: ListPageCount | undefined;
|
|
55
|
+
/** Under the last row, on the count's line: a `Pagination`, or a "Load
|
|
56
|
+
* more" button. */
|
|
57
|
+
pager?: ReactNode;
|
|
46
58
|
children: ReactNode;
|
|
47
59
|
}
|
|
60
|
+
/** What a list's count calls its rows. */
|
|
61
|
+
export interface ListPageCount {
|
|
62
|
+
noun: string;
|
|
63
|
+
nounPlural: string;
|
|
64
|
+
/** Whether a search or filter narrows the list, so the count reads "2 of
|
|
65
|
+
* 7". By default, whether the view's filters are on or `matches` differs
|
|
66
|
+
* from `total`; pass it for a search the page holds. */
|
|
67
|
+
filterActive?: boolean | undefined;
|
|
68
|
+
}
|
|
48
69
|
/** A list's empty state: what is missing, and what to do about it. */
|
|
49
70
|
export interface ListPageEmpty {
|
|
50
71
|
/** A glyph for what the list holds, drawn faded above the title. */
|
|
@@ -58,7 +79,7 @@ export interface ListPageEmpty {
|
|
|
58
79
|
* Given `total`, it also shows the list's states in one shared look: a
|
|
59
80
|
* sketch while loading, the error when loading failed, the empty state with
|
|
60
81
|
* the toolbar hidden, the no-matches state, or the rows. */
|
|
61
|
-
export declare function ListPage({ bleed, toolbar, filterBar, view, loading, error, errorNoun, total, matches, empty, noMatches, toolbarWhenEmpty, children, }: ListPageProps): import("react").JSX.Element;
|
|
82
|
+
export declare function ListPage({ bleed, toolbar, filterBar, view, loading, error, errorNoun, total, matches, empty, noMatches, toolbarWhenEmpty, count, pager, children, }: ListPageProps): import("react").JSX.Element;
|
|
62
83
|
export declare function ListRow({ href, onActivate, items, goTo, openMenu, pinnedActions, selected, selectionActive, onRowSelect, leading, className, children, }: {
|
|
63
84
|
/** Where the row goes. Its `NameCell` with the same href becomes a link
|
|
64
85
|
* covering the row; a click the link doesn't catch still goes there. */
|
package/dist/list/ListPage.js
CHANGED
|
@@ -5,8 +5,10 @@ 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
7
|
import { CellPlacementContext, STACKED_ITEM, Table } from "../components/Table.js";
|
|
8
|
+
import { ListCountText, ListFooterCountContext } from "./listSearch.js";
|
|
8
9
|
import { ListFilterBar } from "./ListFilterBar.js";
|
|
9
10
|
import { ListActionsHeader, ListError, ListSkeleton, StretchRowLinkContext } from "./ListTable.js";
|
|
11
|
+
import { ListToolbar, toolbarCount } from "./ListToolbar.js";
|
|
10
12
|
import { MoreButton, rowContextMenuProps, useRowMenuActive } from "./rowMenu.js";
|
|
11
13
|
import { Children, cloneElement, createContext, isValidElement, useContext, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
12
14
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -47,13 +49,25 @@ var STAYS_BESIDE_STACK = "@max-[36rem]:align-top @max-[36rem]:pt-2.5";
|
|
|
47
49
|
* Given `total`, it also shows the list's states in one shared look: a
|
|
48
50
|
* sketch while loading, the error when loading failed, the empty state with
|
|
49
51
|
* the toolbar hidden, the no-matches state, or the rows. */
|
|
50
|
-
function ListPage({ bleed = false, toolbar, filterBar, view, loading = false, error, errorNoun, total, matches, empty, noMatches, toolbarWhenEmpty = false, children }) {
|
|
52
|
+
function ListPage({ bleed = false, toolbar, filterBar, view, loading = false, error, errorNoun, total, matches, empty, noMatches, toolbarWhenEmpty = false, count, pager, children }) {
|
|
51
53
|
const failed = !loading && error !== void 0 && error !== null && !total;
|
|
52
54
|
const known = total !== void 0 && !loading && !failed;
|
|
53
55
|
const isEmpty = known && total === 0;
|
|
54
56
|
const noneMatch = known && !isEmpty && (matches ?? total) === 0;
|
|
55
57
|
const controls = total === void 0 || toolbarWhenEmpty || known && total > 0;
|
|
56
|
-
const
|
|
58
|
+
const givenBar = filterBar === void 0 ? view?.filterState && view.filterState.fields.length > 0 ? /* @__PURE__ */ jsx(ListFilterBar, { filters: view.filterState }) : null : filterBar;
|
|
59
|
+
const fromToolbar = isValidElement(toolbar) && toolbar.type === ListToolbar ? toolbarCount(toolbar.props) : void 0;
|
|
60
|
+
const counted = count && total !== void 0 ? {
|
|
61
|
+
total,
|
|
62
|
+
matches: matches ?? total,
|
|
63
|
+
filterActive: count.filterActive ?? ((view?.filterState?.count ?? 0) > 0 || (matches ?? total) !== total),
|
|
64
|
+
noun: count.noun,
|
|
65
|
+
nounPlural: count.nounPlural
|
|
66
|
+
} : fromToolbar;
|
|
67
|
+
const showsRows = !loading && !failed && !isEmpty && !noneMatch && (!counted || counted.matches > 0);
|
|
68
|
+
const footerCount = showsRows ? counted : void 0;
|
|
69
|
+
const shareLine = counted !== void 0 && !!toolbar && isValidElement(givenBar) && givenBar.type === ListFilterBar;
|
|
70
|
+
const bar = shareLine ? cloneElement(givenBar, { end: toolbar }) : givenBar;
|
|
57
71
|
let body = children;
|
|
58
72
|
if (loading) body = /* @__PURE__ */ jsx(ListSkeleton, {});
|
|
59
73
|
else if (failed) body = /* @__PURE__ */ jsx(ListError, {
|
|
@@ -70,23 +84,32 @@ function ListPage({ bleed = false, toolbar, filterBar, view, loading = false, er
|
|
|
70
84
|
});
|
|
71
85
|
return /* @__PURE__ */ jsxs("div", {
|
|
72
86
|
className: cx("flex h-full min-h-0 flex-col", bleed && "-m-4"),
|
|
73
|
-
children: [
|
|
74
|
-
|
|
87
|
+
children: [/* @__PURE__ */ jsxs(ListFooterCountContext, {
|
|
88
|
+
value: counted !== void 0,
|
|
89
|
+
children: [!!toolbar && !shareLine && controls && /* @__PURE__ */ jsx("div", {
|
|
75
90
|
className: "shrink-0 px-4",
|
|
76
91
|
children: toolbar
|
|
77
|
-
}),
|
|
78
|
-
!!bar && controls && /* @__PURE__ */ jsx("div", {
|
|
92
|
+
}), !!bar && controls && /* @__PURE__ */ jsx("div", {
|
|
79
93
|
className: "shrink-0 px-4",
|
|
80
94
|
children: bar
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
})]
|
|
96
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
97
|
+
className: "relative @container min-h-0 flex-1 overflow-auto",
|
|
98
|
+
children: [/* @__PURE__ */ jsx(ListRowActionsProvider, {
|
|
99
|
+
view,
|
|
100
|
+
children: body
|
|
101
|
+
}), (footerCount || showsRows && !!pager) && /* @__PURE__ */ jsx(ListFooter, {
|
|
102
|
+
count: footerCount,
|
|
103
|
+
pager
|
|
104
|
+
})]
|
|
105
|
+
})]
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** The line under a list's last row: its count at the rows' start, its pager at their end. */
|
|
109
|
+
function ListFooter({ count, pager }) {
|
|
110
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
111
|
+
className: "flex min-h-10 flex-wrap items-center gap-x-3 px-4 empty:hidden [&>:not([role=status])]:ml-auto [&>nav]:flex-1 [&>nav]:px-0 [&>nav>div:first-child]:justify-end",
|
|
112
|
+
children: [count && /* @__PURE__ */ jsx(ListCountText, { ...count }), pager]
|
|
90
113
|
});
|
|
91
114
|
}
|
|
92
115
|
var STACKED = { stacked: true };
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import { type ListSearch } from './listSearch.js';
|
|
2
|
+
import { type ListCountProps, type ListSearch } from './listSearch.js';
|
|
3
3
|
import type { ListView } from './listView.js';
|
|
4
4
|
export interface ListToolbarProps<T> {
|
|
5
5
|
/** What the count says: how many there are and, while a search or filter
|
|
6
6
|
* is on, how many match. `filterActive` defaults to whether the search or
|
|
7
|
-
* the view's filters narrow the list.
|
|
8
|
-
|
|
7
|
+
* the view's filters narrow the list. As a `ListPage`'s `toolbar`, the
|
|
8
|
+
* page shows it under the last row, as its own `count` would; anywhere
|
|
9
|
+
* else, it leads the toolbar. */
|
|
10
|
+
count?: {
|
|
9
11
|
total: number;
|
|
10
12
|
matches: number;
|
|
11
13
|
filterActive?: boolean | undefined;
|
|
@@ -25,7 +27,11 @@ export interface ListToolbarProps<T> {
|
|
|
25
27
|
/** More controls, before the filter and display menus. */
|
|
26
28
|
children?: ReactNode;
|
|
27
29
|
}
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
/** The count a `ListToolbar` was given, as a `ListPage` shows it under its rows. */
|
|
31
|
+
export declare function toolbarCount<T>({ count, view, search, }: ListToolbarProps<T>): ListCountProps | undefined;
|
|
32
|
+
/** A list page's toolbar in the shared order, at its end: the page's own
|
|
33
|
+
* controls, the filter menu, the display options, the search and an action.
|
|
34
|
+
* Give it to `ListPage` as its `toolbar`; the list's count goes under its
|
|
35
|
+
* rows (see `ListPage`'s `count`), and while filter chips show, the toolbar
|
|
36
|
+
* sits at the end of their first line. */
|
|
37
|
+
export declare function ListToolbar<T>(props: ListToolbarProps<T>): import("react").JSX.Element;
|
package/dist/list/ListToolbar.js
CHANGED
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
import { useStrings } from "../components/Provider.js";
|
|
2
|
+
import { ListCountText, ListFooterCountContext, ListSearchControl, ListToolbarInlineContext } from "./listSearch.js";
|
|
2
3
|
import { ListDisplayMenu, ListFilterMenu } from "./ListViewControls.js";
|
|
3
|
-
import {
|
|
4
|
+
import { useContext } from "react";
|
|
4
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
//#region src/list/ListToolbar.tsx
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
function ListToolbar({ count, view, search, searchLabel, searchPlaceholder, action, children }) {
|
|
10
|
-
const { common } = useStrings();
|
|
7
|
+
/** The count a `ListToolbar` was given, as a `ListPage` shows it under its rows. */
|
|
8
|
+
function toolbarCount({ count, view, search }) {
|
|
9
|
+
if (!count) return;
|
|
11
10
|
const filterActive = count.filterActive ?? (search !== void 0 && (search.filter.trim() !== "" || search.terms.length > 0) || (view?.activeFilterCount ?? 0) > 0);
|
|
11
|
+
return {
|
|
12
|
+
total: count.total,
|
|
13
|
+
matches: count.matches,
|
|
14
|
+
filterActive,
|
|
15
|
+
noun: count.noun,
|
|
16
|
+
nounPlural: count.nounPlural
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** A list page's toolbar in the shared order, at its end: the page's own
|
|
20
|
+
* controls, the filter menu, the display options, the search and an action.
|
|
21
|
+
* Give it to `ListPage` as its `toolbar`; the list's count goes under its
|
|
22
|
+
* rows (see `ListPage`'s `count`), and while filter chips show, the toolbar
|
|
23
|
+
* sits at the end of their first line. */
|
|
24
|
+
function ListToolbar(props) {
|
|
25
|
+
const { view, search, searchLabel, searchPlaceholder, action, children } = props;
|
|
26
|
+
const { common } = useStrings();
|
|
27
|
+
const count = useContext(ListFooterCountContext) ? void 0 : toolbarCount(props);
|
|
28
|
+
const inline = useContext(ListToolbarInlineContext);
|
|
12
29
|
return /* @__PURE__ */ jsxs("div", {
|
|
13
|
-
className: "flex min-h-10 items-center justify-
|
|
14
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
15
|
-
className: "shrink-0 whitespace-nowrap",
|
|
16
|
-
children: /* @__PURE__ */ jsx(
|
|
17
|
-
total: count.total,
|
|
18
|
-
matches: count.matches,
|
|
19
|
-
filterActive,
|
|
20
|
-
noun: count.noun,
|
|
21
|
-
nounPlural: count.nounPlural
|
|
22
|
-
})
|
|
30
|
+
className: inline ? "flex items-center justify-end gap-3" : "flex min-h-10 items-center justify-end gap-3 py-1",
|
|
31
|
+
children: [count && /* @__PURE__ */ jsx("div", {
|
|
32
|
+
className: "mr-auto flex h-8 shrink-0 items-center whitespace-nowrap",
|
|
33
|
+
children: /* @__PURE__ */ jsx(ListCountText, { ...count })
|
|
23
34
|
}), /* @__PURE__ */ jsxs("div", {
|
|
24
35
|
className: "flex min-w-0 items-center gap-1",
|
|
25
36
|
children: [
|
|
@@ -40,4 +51,4 @@ function ListToolbar({ count, view, search, searchLabel, searchPlaceholder, acti
|
|
|
40
51
|
});
|
|
41
52
|
}
|
|
42
53
|
//#endregion
|
|
43
|
-
export { ListToolbar };
|
|
54
|
+
export { ListToolbar, toolbarCount };
|
package/dist/list/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { ActionToolbar, type ToolbarAction, ToolbarButton, type ToolbarItem, } from './ActionToolbar.js';
|
|
2
2
|
export { type ActiveFilter, applyFilterList, DATE_PRESETS, type DatePreset, dateFilterRange, decodeFilters, encodeFilters, type FilterField, type FilterKind, type FilterOperator, type FilterOption, type FilterRowValue, filterParamKeys, matchFilter, type QuickFilter, sameFilters, withFilterParams, } from './filterModel.js';
|
|
3
3
|
export { AddFilterMenu, type AddFilterMenuProps, FilterChip, ListFilterBar, type ListFilterBarProps, } from './ListFilterBar.js';
|
|
4
|
-
export { GroupRow, ListColumns, ListPage, type ListPageEmpty, type ListPageProps, ListRow, ListRowActionsProvider, useListNavigate, } from './ListPage.js';
|
|
4
|
+
export { GroupRow, ListColumns, ListPage, type ListPageCount, type ListPageEmpty, type ListPageProps, ListRow, ListRowActionsProvider, useListNavigate, } from './ListPage.js';
|
|
5
5
|
export { actionsColumnWidth, ListActionsHeader, ListCellText, ListError, ListSelectAllHeader, ListSkeleton, listTableFixedProps, listTableProps, NAME_MIN_WIDTH_PX, NameCell, RowSelectCheckbox, STRETCHED_LINK, } from './ListTable.js';
|
|
6
6
|
export { ListToolbar, type ListToolbarProps } from './ListToolbar.js';
|
|
7
7
|
export { ListDisplayMenu, ListFilterMenu } from './ListViewControls.js';
|
package/dist/list/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { ActionToolbar, ToolbarButton } from "./ActionToolbar.js";
|
|
2
2
|
import { DATE_PRESETS, applyFilterList, dateFilterRange, decodeFilters, encodeFilters, filterParamKeys, matchFilter, sameFilters, withFilterParams } from "./filterModel.js";
|
|
3
|
+
import { ListCount, ListSearchControl, useListSearch } from "./listSearch.js";
|
|
3
4
|
import { AddFilterMenu, FilterChip, ListFilterBar } from "./ListFilterBar.js";
|
|
4
5
|
import { ListActionsHeader, ListCellText, ListError, ListSelectAllHeader, ListSkeleton, NAME_MIN_WIDTH_PX, NameCell, RowSelectCheckbox, STRETCHED_LINK, actionsColumnWidth, listTableFixedProps, listTableProps } from "./ListTable.js";
|
|
5
|
-
import { MoreButton, assembleRowMenu, rowContextMenuProps, useCopySubmenu, useRowMenuActive } from "./rowMenu.js";
|
|
6
|
-
import { GroupRow, ListColumns, ListPage, ListRow, ListRowActionsProvider, useListNavigate } from "./ListPage.js";
|
|
7
6
|
import { useListFilters } from "./useListFilters.js";
|
|
8
7
|
import { DEFAULT_ORDER, FAVORITE_ORDER, useListView } from "./listView.js";
|
|
9
8
|
import { ListDisplayMenu, ListFilterMenu } from "./ListViewControls.js";
|
|
10
|
-
import { ListCount, ListSearchControl, useListSearch } from "./listSearch.js";
|
|
11
9
|
import { ListToolbar } from "./ListToolbar.js";
|
|
10
|
+
import { MoreButton, assembleRowMenu, rowContextMenuProps, useCopySubmenu, useRowMenuActive } from "./rowMenu.js";
|
|
11
|
+
import { GroupRow, ListColumns, ListPage, ListRow, ListRowActionsProvider, useListNavigate } from "./ListPage.js";
|
|
12
12
|
import { HoverCardRow, UserHoverCard } from "./UserHoverCard.js";
|
|
13
13
|
export { ActionToolbar, AddFilterMenu, DATE_PRESETS, DEFAULT_ORDER, FAVORITE_ORDER, FilterChip, GroupRow, HoverCardRow, ListActionsHeader, ListCellText, ListColumns, ListCount, ListDisplayMenu, ListError, ListFilterBar, ListFilterMenu, ListPage, ListRow, ListRowActionsProvider, ListSearchControl, ListSelectAllHeader, ListSkeleton, ListToolbar, MoreButton, NAME_MIN_WIDTH_PX, NameCell, RowSelectCheckbox, STRETCHED_LINK, ToolbarButton, UserHoverCard, actionsColumnWidth, applyFilterList, assembleRowMenu, dateFilterRange, decodeFilters, encodeFilters, filterParamKeys, listTableFixedProps, listTableProps, matchFilter, rowContextMenuProps, sameFilters, useCopySubmenu, useListFilters, useListNavigate, useListSearch, useListView, useRowMenuActive, withFilterParams };
|
|
@@ -18,10 +18,26 @@ export declare function ListSearchControl({ search, placeholder, label: labelPro
|
|
|
18
18
|
placeholder: string;
|
|
19
19
|
label?: string | undefined;
|
|
20
20
|
}): import("react").JSX.Element;
|
|
21
|
-
|
|
21
|
+
/** What a list's count needs: how many there are, how many a search or filter leaves, and the
|
|
22
|
+
* noun in both numbers. */
|
|
23
|
+
export interface ListCountProps {
|
|
22
24
|
total: number;
|
|
23
25
|
matches: number;
|
|
24
26
|
filterActive: boolean;
|
|
25
27
|
noun: string;
|
|
26
28
|
nounPlural: string;
|
|
27
|
-
}
|
|
29
|
+
}
|
|
30
|
+
/** True inside a `ListPage` that shows its count under its rows, so a count placed in its toolbar
|
|
31
|
+
* by hand gives way rather than showing twice. */
|
|
32
|
+
export declare const ListFooterCountContext: import("react").Context<boolean>;
|
|
33
|
+
/** True where a toolbar shares a line with the list's filter chips, at its end: a `ListToolbar`
|
|
34
|
+
* there drops its own row's height and padding. */
|
|
35
|
+
export declare const ListToolbarInlineContext: import("react").Context<boolean>;
|
|
36
|
+
/** The count's words, "7 tokens" or "2 of 7 tokens", as a status so a screen reader hears the
|
|
37
|
+
* count change as a filter narrows it. */
|
|
38
|
+
export declare function ListCountText({ total, matches, filterActive, noun, nounPlural }: ListCountProps): import("react").JSX.Element;
|
|
39
|
+
/** A list's count, placed by hand.
|
|
40
|
+
* @deprecated Give `ListPage` a `count` instead (with its `total` and `matches`): it shows the
|
|
41
|
+
* count under the last row, beside a `pager`, and hides it while the list shows its empty or
|
|
42
|
+
* no-matches state. Inside a `ListPage` that shows its own count, this renders nothing. */
|
|
43
|
+
export declare function ListCount(props: ListCountProps): import("react").JSX.Element;
|
package/dist/list/listSearch.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Tooltip } from "../components/Tooltip.js";
|
|
|
5
5
|
import { useLocale, usePersistedState, useStrings } from "../components/Provider.js";
|
|
6
6
|
import { isTextEntryContext } from "../components/shortcutKey.js";
|
|
7
7
|
import { formatInteger } from "../format.js";
|
|
8
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
8
|
+
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
9
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
//#region src/list/listSearch.tsx
|
|
11
11
|
var NO_TERMS = [];
|
|
@@ -133,18 +133,34 @@ function ListSearchControl({ search, placeholder, label: labelProp }) {
|
|
|
133
133
|
})
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
|
-
|
|
136
|
+
/** True inside a `ListPage` that shows its count under its rows, so a count placed in its toolbar
|
|
137
|
+
* by hand gives way rather than showing twice. */
|
|
138
|
+
var ListFooterCountContext = createContext(false);
|
|
139
|
+
/** True where a toolbar shares a line with the list's filter chips, at its end: a `ListToolbar`
|
|
140
|
+
* there drops its own row's height and padding. */
|
|
141
|
+
var ListToolbarInlineContext = createContext(false);
|
|
142
|
+
/** The count's words, "7 tokens" or "2 of 7 tokens", as a status so a screen reader hears the
|
|
143
|
+
* count change as a filter narrows it. */
|
|
144
|
+
function ListCountText({ total, matches, filterActive, noun, nounPlural }) {
|
|
137
145
|
const { list: t } = useStrings();
|
|
138
146
|
const locale = useLocale();
|
|
139
147
|
const count = (n) => formatInteger(n, locale);
|
|
148
|
+
return /* @__PURE__ */ jsx("span", {
|
|
149
|
+
role: "status",
|
|
150
|
+
className: "text-[13px] text-(--theme-muted-text-color)",
|
|
151
|
+
children: filterActive ? t.matchCount(count(matches), count(total), nounPlural) : t.count(count(total), total === 1 ? noun : nounPlural)
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/** A list's count, placed by hand.
|
|
155
|
+
* @deprecated Give `ListPage` a `count` instead (with its `total` and `matches`): it shows the
|
|
156
|
+
* count under the last row, beside a `pager`, and hides it while the list shows its empty or
|
|
157
|
+
* no-matches state. Inside a `ListPage` that shows its own count, this renders nothing. */
|
|
158
|
+
function ListCount(props) {
|
|
159
|
+
if (useContext(ListFooterCountContext)) return /* @__PURE__ */ jsx("span", { "aria-hidden": "true" });
|
|
140
160
|
return /* @__PURE__ */ jsx("div", {
|
|
141
161
|
className: "flex items-center h-8",
|
|
142
|
-
children: /* @__PURE__ */ jsx(
|
|
143
|
-
role: "status",
|
|
144
|
-
className: "text-[13px] text-(--theme-muted-text-color)",
|
|
145
|
-
children: filterActive ? t.matchCount(count(matches), count(total), nounPlural) : t.count(count(total), total === 1 ? noun : nounPlural)
|
|
146
|
-
})
|
|
162
|
+
children: /* @__PURE__ */ jsx(ListCountText, { ...props })
|
|
147
163
|
});
|
|
148
164
|
}
|
|
149
165
|
//#endregion
|
|
150
|
-
export { ListCount, ListSearchControl, useListSearch };
|
|
166
|
+
export { ListCount, ListCountText, ListFooterCountContext, ListSearchControl, ListToolbarInlineContext, useListSearch };
|