@cosmicdrift/kumiko-renderer 0.392.0 → 0.392.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web/primitives/data-table/toolbar.d.ts +2 -1
- package/dist/web/primitives/data-table/toolbar.js +16 -3
- package/dist/web/primitives/data-table/use-data-table-state.d.ts +1 -0
- package/dist/web/primitives/data-table/use-data-table-state.js +3 -0
- package/dist/web/primitives/data-table.js +3 -2
- package/dist/web/primitives/layout-elements.js +2 -2
- package/package.json +5 -5
- package/src/changes.json +6 -0
|
@@ -2,5 +2,6 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
import type { DataTableModel } from "./use-data-table-state.ts";
|
|
3
3
|
export declare function buildModelFacetCluster(m: DataTableModel): ReactNode;
|
|
4
4
|
export declare function hasToolbarContent(m: DataTableModel, facetCluster: ReactNode): boolean;
|
|
5
|
+
export declare function toolbarMergesDescription(m: DataTableModel, facetCluster: ReactNode): boolean;
|
|
5
6
|
export declare function renderToolbar(m: DataTableModel, facetCluster: ReactNode): ReactNode;
|
|
6
|
-
export declare function renderToolbarDescription(m: DataTableModel, hasToolbar: boolean): ReactNode;
|
|
7
|
+
export declare function renderToolbarDescription(m: DataTableModel, hasToolbar: boolean, inToolbarRow?: boolean): ReactNode;
|
|
@@ -40,14 +40,27 @@ function renderFacetToggle(m, facetCluster) {
|
|
|
40
40
|
const { testId, facetsOpenNarrow, setFacetsOpenNarrow } = m;
|
|
41
41
|
return (_jsxs(_Fragment, { children: [_jsx(UiButton, { type: "button", variant: "outline", size: "icon", className: "size-11", "aria-label": m.tableTranslate?.("kumiko.list.filter.toggle") ?? "Filter", "aria-expanded": facetsOpenNarrow, "data-testid": testId !== undefined ? `${testId}-filter-toggle` : "filter-toggle", onClick: () => setFacetsOpenNarrow((open) => !open), children: _jsx(SlidersHorizontal, { className: "size-4", "aria-hidden": "true" }) }), facetsOpenNarrow && _jsx("div", { className: "w-full", children: facetCluster })] }));
|
|
42
42
|
}
|
|
43
|
+
// With only end content (no search, no facets) the row has room for the description,
|
|
44
|
+
// so both share one line instead of stacking the description under the actions.
|
|
45
|
+
export function toolbarMergesDescription(m, facetCluster) {
|
|
46
|
+
return (m.toolbarDescription !== undefined &&
|
|
47
|
+
m.toolbarStart === undefined &&
|
|
48
|
+
facetCluster === undefined &&
|
|
49
|
+
toolbarEndHasContent(m.view, m.exportList, m.toolbarEnd));
|
|
50
|
+
}
|
|
43
51
|
export function renderToolbar(m, facetCluster) {
|
|
44
52
|
const { testId, toolbarStart } = m;
|
|
45
53
|
const collapsesFacetsBehindButton = m.isNarrow && m.fillsHeight && facetCluster !== undefined;
|
|
46
|
-
|
|
54
|
+
const mergesDescription = toolbarMergesDescription(m, facetCluster);
|
|
55
|
+
return (_jsxs("div", { "data-testid": testId !== undefined ? `${testId}-toolbar` : "render-list-toolbar", className: cn(toolbarClassName(m), mergesDescription && "justify-between", mergesDescription && m.fillsHeight && !m.isNarrow && "md:py-2",
|
|
56
|
+
// A dashboard panel title above has no inset; the description must share its left edge.
|
|
57
|
+
mergesDescription && m.fillsHeight && m.insideEmbeddedScreen && "px-0"), children: [mergesDescription && renderToolbarDescription(m, true, true), toolbarStart !== undefined && renderToolbarStart(m, toolbarStart), collapsesFacetsBehindButton ? renderFacetToggle(m, facetCluster) : facetCluster, _jsx(ToolbarEndCluster, { view: m.view, columnLabels: new Map(m.schemaColumns.map((col) => [col.field, col.label])), exportList: m.exportList, toolbarEnd: m.toolbarEnd })] }));
|
|
47
58
|
}
|
|
48
|
-
export function renderToolbarDescription(m, hasToolbar) {
|
|
59
|
+
export function renderToolbarDescription(m, hasToolbar, inToolbarRow = false) {
|
|
49
60
|
const { testId, toolbarDescription, fillsHeight, isNarrow } = m;
|
|
50
61
|
if (toolbarDescription === undefined)
|
|
51
62
|
return null;
|
|
52
|
-
return (_jsx("p", { "data-testid": testId !== undefined ? `${testId}-description` : "render-list-description", className: cn("m-0
|
|
63
|
+
return (_jsx("p", { "data-testid": testId !== undefined ? `${testId}-description` : "render-list-description", className: cn("m-0 text-sm text-muted-foreground",
|
|
64
|
+
// min-w-48 lets the actions wrap below the text instead of squeezing it.
|
|
65
|
+
inToolbarRow ? "min-w-48 flex-1" : "pb-2", !inToolbarRow && fillsHeight && (isNarrow ? "px-3" : "px-6"), !inToolbarRow && fillsHeight && !hasToolbar && "pt-4"), children: toolbarDescription }));
|
|
53
66
|
}
|
|
@@ -30,6 +30,7 @@ export type DataTableModel = Omit<DataTableProps, "columns"> & {
|
|
|
30
30
|
readonly rowTestId: (row: ListRowViewModel) => string;
|
|
31
31
|
readonly expansion: RowExpansion | undefined;
|
|
32
32
|
readonly fillsHeight: boolean;
|
|
33
|
+
readonly insideEmbeddedScreen: boolean;
|
|
33
34
|
readonly selection: ReturnType<typeof useBulkSelection>;
|
|
34
35
|
readonly headerRowRef: ReturnType<typeof useMeasuredColumnWidths>["headerRowRef"];
|
|
35
36
|
readonly pins: ColumnPins<ListColumnViewModel>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useId, useState } from "react";
|
|
2
|
+
import { useIsEmbeddedScreen } from "../../../context/embedded-screen-context.js";
|
|
2
3
|
import { useOptionalLocale, useOptionalTranslation } from "../../../index.js";
|
|
3
4
|
import { useIsNarrowViewport } from "../use-narrow-viewport.js";
|
|
4
5
|
import { isPinningActive, isResizingActive } from "./column-layout-mode.js";
|
|
@@ -55,6 +56,7 @@ export function useDataTableState(props) {
|
|
|
55
56
|
const expansion = resolveRowExpansion(props);
|
|
56
57
|
const isRowExpandable = expansion !== undefined;
|
|
57
58
|
const fillsHeight = scrollBody === true;
|
|
59
|
+
const insideEmbeddedScreen = useIsEmbeddedScreen();
|
|
58
60
|
const selection = useBulkSelection({
|
|
59
61
|
bulk,
|
|
60
62
|
rows,
|
|
@@ -110,6 +112,7 @@ export function useDataTableState(props) {
|
|
|
110
112
|
rowTestId,
|
|
111
113
|
expansion,
|
|
112
114
|
fillsHeight,
|
|
115
|
+
insideEmbeddedScreen,
|
|
113
116
|
selection,
|
|
114
117
|
headerRowRef,
|
|
115
118
|
pins,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
3
|
import { renderTableContent } from "./data-table/table-content.js";
|
|
4
|
-
import { buildModelFacetCluster, hasToolbarContent, renderToolbar, renderToolbarDescription, } from "./data-table/toolbar.js";
|
|
4
|
+
import { buildModelFacetCluster, hasToolbarContent, renderToolbar, renderToolbarDescription, toolbarMergesDescription, } from "./data-table/toolbar.js";
|
|
5
5
|
import { useDataTableState } from "./data-table/use-data-table-state.js";
|
|
6
6
|
import { screenPaddingClassName } from "./layout.js";
|
|
7
7
|
// `screenPadding`: on a list screen this wrapper IS the screen container,
|
|
@@ -28,10 +28,11 @@ export function DefaultDataTable(props) {
|
|
|
28
28
|
const model = useDataTableState(props);
|
|
29
29
|
const facetCluster = buildModelFacetCluster(model);
|
|
30
30
|
const hasToolbar = hasToolbarContent(model, facetCluster);
|
|
31
|
+
const mergesDescription = toolbarMergesDescription(model, facetCluster);
|
|
31
32
|
// dashboard-01 pattern: the toolbar (search + facets + "+ New") sits ABOVE
|
|
32
33
|
// the table in the same padding block; no separate bg bar, no screen title
|
|
33
34
|
// (that lives in the shell's breadcrumb). The toolbar is NOT sticky: lists
|
|
34
35
|
// typically scroll with the page container, not internally. Sticky would
|
|
35
36
|
// compete with the topbar.
|
|
36
|
-
return (_jsxs("div", { className: wrapperClassName(model), children: [!model.fillsHeight && renderToolbarDescription(model, hasToolbar), hasToolbar && renderToolbar(model, facetCluster), model.fillsHeight && renderToolbarDescription(model, hasToolbar), renderTableContent(model)] }));
|
|
37
|
+
return (_jsxs("div", { className: wrapperClassName(model), children: [!mergesDescription && !model.fillsHeight && renderToolbarDescription(model, hasToolbar), hasToolbar && renderToolbar(model, facetCluster), !mergesDescription && model.fillsHeight && renderToolbarDescription(model, hasToolbar), renderTableContent(model)] }));
|
|
37
38
|
}
|
|
@@ -21,8 +21,8 @@ export function DefaultSection({ id, title, subtitle, children, actions, variant
|
|
|
21
21
|
// footer — a title-less section still draws the row when actions are
|
|
22
22
|
// present, so a hideTitle tabs-Section with `actions` isn't stranded.
|
|
23
23
|
const TitleTag = insideScreenForm ? "h2" : "h3";
|
|
24
|
-
const titleBlock = title !== undefined || subtitle !== undefined ? (_jsxs("div", { className: "flex flex-col gap-1", children: [title !== undefined && (_jsxs(TitleTag, { "data-testid": testId !== undefined ? `${testId}-title` : undefined, className: "flex items-center gap-2 text-base font-semibold leading-none tracking-tight", children: [icon !== undefined && _jsx(Icon, { name: icon, className: "size-4 text-muted-foreground" }), title] })), subtitle !== undefined && (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-subtitle` : undefined, className: cn("text-sm text-muted-foreground", insideScreenForm && "text-[13px] text-foreground-secondary"), children: subtitle }))] })) : null;
|
|
25
|
-
const header = titleBlock !== null || actions !== undefined ? (_jsxs("div", { className: "flex items-start justify-between gap-4", children: [titleBlock, actions !== undefined && (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions` : undefined, className: "ml-auto flex shrink-0 items-center gap-2", children: actions }))] })) : null;
|
|
24
|
+
const titleBlock = title !== undefined || subtitle !== undefined ? (_jsxs("div", { className: "flex min-w-48 flex-1 flex-col gap-1", children: [title !== undefined && (_jsxs(TitleTag, { "data-testid": testId !== undefined ? `${testId}-title` : undefined, className: "flex items-center gap-2 text-base font-semibold leading-none tracking-tight", children: [icon !== undefined && _jsx(Icon, { name: icon, className: "size-4 text-muted-foreground" }), title] })), subtitle !== undefined && (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-subtitle` : undefined, className: cn("text-sm text-muted-foreground", insideScreenForm && "text-[13px] text-foreground-secondary"), children: subtitle }))] })) : null;
|
|
25
|
+
const header = titleBlock !== null || actions !== undefined ? (_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-x-4 gap-y-3", children: [titleBlock, actions !== undefined && (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions` : undefined, className: "ml-auto flex shrink-0 items-center gap-2", children: actions }))] })) : null;
|
|
26
26
|
// Inside a form: divider-less section WITHOUT a card surface of its own.
|
|
27
27
|
// The separator lines BETWEEN sections come from the divide-y wrapper in
|
|
28
28
|
// the form.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer",
|
|
3
|
-
"version": "0.392.
|
|
3
|
+
"version": "0.392.1",
|
|
4
4
|
"description": "React renderer for Kumiko screens. The root export holds the platform-agnostic logic (primitives contract, hooks, KumikoScreen, navigation and SSE abstractions); ./headless the DOM-free UI logic, ./live the HTTP dispatcher, ./web the browser bindings (HTML primitives, history navigation, EventSource live events, createKumikoApp).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@cosmicdrift/kumiko-framework": "0.392.
|
|
52
|
-
"@cosmicdrift/kumiko-types": "0.392.
|
|
51
|
+
"@cosmicdrift/kumiko-framework": "0.392.1",
|
|
52
|
+
"@cosmicdrift/kumiko-types": "0.392.1",
|
|
53
53
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
54
54
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
55
55
|
"@radix-ui/react-label": "^2.1.8",
|
|
@@ -92,8 +92,8 @@
|
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
-
"@cosmicdrift/kumiko-locale-de": "0.392.
|
|
96
|
-
"@cosmicdrift/kumiko-testing": "0.392.
|
|
95
|
+
"@cosmicdrift/kumiko-locale-de": "0.392.1",
|
|
96
|
+
"@cosmicdrift/kumiko-testing": "0.392.1",
|
|
97
97
|
"@tailwindcss/cli": "^4.3.0",
|
|
98
98
|
"@testing-library/react": "^16.3.2",
|
|
99
99
|
"@testing-library/user-event": "^14.6.1",
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.392.1",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "List descriptions sit beside the toolbar actions",
|
|
6
|
+
"detail": "A description plus a toolbar with only end content (no search, no facets) renders as one row with the actions on the right, in embedded and full-height lists. Before, a full-height list put the description under the create button. With search or facets the layout is unchanged. Section header actions wrap below the title on narrow widths."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.392.0",
|
|
4
10
|
"type": "improvement",
|