@wheelhouse/ui 0.2.6 → 0.2.8
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/blocks/columns/column-popover-panel-header.d.ts +10 -0
- package/dist/blocks/columns/column-popover-panel-header.d.ts.map +1 -0
- package/dist/blocks/columns/column-popover-panel-header.js +9 -0
- package/dist/blocks/columns/columns-add-view.d.ts +12 -0
- package/dist/blocks/columns/columns-add-view.d.ts.map +1 -0
- package/dist/blocks/columns/columns-add-view.js +34 -0
- package/dist/blocks/columns/columns-types.d.ts +26 -1
- package/dist/blocks/columns/columns-types.d.ts.map +1 -1
- package/dist/blocks/columns/columns-types.js +9 -2
- package/dist/blocks/columns/columns-utils.d.ts +7 -3
- package/dist/blocks/columns/columns-utils.d.ts.map +1 -1
- package/dist/blocks/columns/columns-utils.js +28 -6
- package/dist/blocks/columns/columns.d.ts.map +1 -1
- package/dist/blocks/columns/columns.js +106 -68
- package/dist/blocks/columns/columns.stories.d.ts +1 -0
- package/dist/blocks/columns/columns.stories.d.ts.map +1 -1
- package/dist/blocks/columns/columns.stories.js +19 -4
- package/dist/blocks/columns/index.d.ts +1 -1
- package/dist/blocks/columns/index.d.ts.map +1 -1
- package/dist/blocks/columns/index.js +1 -1
- package/dist/components/avatar/avatar.d.ts +3 -2
- package/dist/components/avatar/avatar.d.ts.map +1 -1
- package/dist/components/avatar/avatar.js +3 -2
- package/dist/components/avatar/avatar.stories.d.ts.map +1 -1
- package/dist/components/avatar/avatar.stories.js +7 -0
- package/dist/components/button/button.d.ts +3 -3
- package/dist/components/button/button.js +7 -7
- package/dist/components/button-group/button-group.js +2 -2
- package/dist/components/calendar/calendar.js +2 -2
- package/dist/components/data-grid/data-grid-column-filter.js +1 -1
- package/dist/components/field/field.d.ts +1 -1
- package/dist/components/field/field.d.ts.map +1 -1
- package/dist/components/filters/filter-date-metric-value.js +1 -1
- package/dist/components/filters/filters.d.ts.map +1 -1
- package/dist/components/filters/filters.js +3 -2
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +2 -0
- package/dist/components/input/input.d.ts +2 -2
- package/dist/components/input/input.d.ts.map +1 -1
- package/dist/components/input/input.js +4 -3
- package/dist/components/input/input.stories.d.ts +1 -1
- package/dist/components/input/input.stories.d.ts.map +1 -1
- package/dist/components/input/input.stories.js +1 -1
- package/dist/components/input-group/input-group.d.ts +1 -1
- package/dist/components/number-field/index.d.ts +2 -0
- package/dist/components/number-field/index.d.ts.map +1 -0
- package/dist/components/number-field/index.js +1 -0
- package/dist/components/number-field/number-field.d.ts +59 -0
- package/dist/components/number-field/number-field.d.ts.map +1 -0
- package/dist/components/number-field/number-field.js +49 -0
- package/dist/components/number-field/number-field.stories.d.ts +25 -0
- package/dist/components/number-field/number-field.stories.d.ts.map +1 -0
- package/dist/components/number-field/number-field.stories.js +225 -0
- package/dist/components/overlapping-stack/index.d.ts +3 -0
- package/dist/components/overlapping-stack/index.d.ts.map +1 -0
- package/dist/components/overlapping-stack/index.js +2 -0
- package/dist/components/overlapping-stack/overlapping-stack.d.ts +12 -0
- package/dist/components/overlapping-stack/overlapping-stack.d.ts.map +1 -0
- package/dist/components/overlapping-stack/overlapping-stack.js +45 -0
- package/dist/components/overlapping-stack/overlapping-stack.stories.d.ts +78 -0
- package/dist/components/overlapping-stack/overlapping-stack.stories.d.ts.map +1 -0
- package/dist/components/overlapping-stack/overlapping-stack.stories.js +120 -0
- package/dist/components/overlapping-stack/use-overlapping-stack.d.ts +47 -0
- package/dist/components/overlapping-stack/use-overlapping-stack.d.ts.map +1 -0
- package/dist/components/overlapping-stack/use-overlapping-stack.js +47 -0
- package/dist/components/sidebar/sidebar.js +1 -1
- package/dist/components/textarea/textarea.js +1 -1
- package/dist/components/toggle/toggle.d.ts +3 -3
- package/dist/components/toggle/toggle.js +4 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -3
- package/src/styles/globals.css +6 -24
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export declare function ColumnPopoverPanelHeader({ title, explainer, helpAriaLabel, trailing, className, }: {
|
|
3
|
+
title: string;
|
|
4
|
+
/** When non-empty, shows a help icon with this text in a tooltip. */
|
|
5
|
+
explainer?: string;
|
|
6
|
+
helpAriaLabel?: string;
|
|
7
|
+
trailing?: ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=column-popover-panel-header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"column-popover-panel-header.d.ts","sourceRoot":"","sources":["../../../src/blocks/columns/column-popover-panel-header.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMvC,wBAAgB,wBAAwB,CAAC,EACrC,KAAK,EACL,SAAS,EACT,aAAsB,EACtB,QAAQ,EACR,SAAS,GACZ,EAAE;IACC,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB,2CA6BA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { CircleHelp } from 'lucide-react';
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '../../components/tooltip';
|
|
5
|
+
import { cn } from '../../lib/utils';
|
|
6
|
+
export function ColumnPopoverPanelHeader({ title, explainer, helpAriaLabel = 'Help', trailing, className, }) {
|
|
7
|
+
const helpText = explainer?.trim();
|
|
8
|
+
return (_jsxs("div", { className: cn('flex shrink-0 items-center justify-between gap-2 border-b border-border px-3 py-2.5', className), children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1", children: [_jsx("p", { className: "text-sm font-medium", children: title }), helpText ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx("button", { type: "button", className: "inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", "aria-label": helpAriaLabel }), children: _jsx(CircleHelp, { className: "size-3.5", "aria-hidden": true }) }), _jsx(TooltipContent, { side: "bottom", align: "start", className: "max-w-[min(100vw-2rem,280px)] text-pretty", children: helpText })] })) : null] }), trailing] }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ColumnsI18n, SelectedColumn } from './columns-types';
|
|
2
|
+
import { type ColumnsMenuSection } from './columns-utils';
|
|
3
|
+
export declare function ColumnsAddView({ i18n, sections, value, pendingAddIds, onTogglePendingAdd, onCancel, onConfirm, }: {
|
|
4
|
+
i18n: ColumnsI18n;
|
|
5
|
+
sections: ColumnsMenuSection[];
|
|
6
|
+
value: SelectedColumn[];
|
|
7
|
+
pendingAddIds: Set<string>;
|
|
8
|
+
onTogglePendingAdd: (id: string, checked: boolean) => void;
|
|
9
|
+
onCancel: () => void;
|
|
10
|
+
onConfirm: () => void;
|
|
11
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
//# sourceMappingURL=columns-add-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"columns-add-view.d.ts","sourceRoot":"","sources":["../../../src/blocks/columns/columns-add-view.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAoB,WAAW,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAiC,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAgCzF,wBAAgB,cAAc,CAAC,EAC3B,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,aAAa,EACb,kBAAkB,EAClB,QAAQ,EACR,SAAS,GACZ,EAAE;IACC,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,kBAAkB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3D,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;CACzB,2CA0DA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ChevronRight } from 'lucide-react';
|
|
4
|
+
import { Button } from '../../components/button';
|
|
5
|
+
import { Checkbox } from '../../components/checkbox/checkbox';
|
|
6
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../../components/collapsible';
|
|
7
|
+
import { ScrollArea } from '../../components/scroll-area';
|
|
8
|
+
import { ColumnPopoverPanelHeader } from './column-popover-panel-header';
|
|
9
|
+
import { columnAllowsMultipleInstances } from './columns-utils';
|
|
10
|
+
function isFieldAvailableToAddFromCatalog(field, currentValue) {
|
|
11
|
+
if (columnAllowsMultipleInstances(field))
|
|
12
|
+
return true;
|
|
13
|
+
return !currentValue.some((v) => v.id === field.id);
|
|
14
|
+
}
|
|
15
|
+
const addCatalogFieldRowClassName = 'flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1 text-start text-sm text-foreground hover:bg-accent hover:text-accent-foreground';
|
|
16
|
+
function AddCatalogFieldRow({ field, checked, onToggle, showIcon = false, }) {
|
|
17
|
+
const checkboxId = `columns-add-${field.id}`;
|
|
18
|
+
return (_jsxs("label", { htmlFor: checkboxId, className: addCatalogFieldRowClassName, children: [_jsx(Checkbox, { id: checkboxId, checked: checked, onCheckedChange: (next) => onToggle(field.id, next === true) }), showIcon ? field.icon : null, _jsx("span", { className: "min-w-0 flex-1 truncate", children: field.name })] }));
|
|
19
|
+
}
|
|
20
|
+
export function ColumnsAddView({ i18n, sections, value, pendingAddIds, onTogglePendingAdd, onCancel, onConfirm, }) {
|
|
21
|
+
return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [_jsx(ColumnPopoverPanelHeader, { title: i18n.addColumnsAction, explainer: i18n.addColumnsStagedExplainer }), _jsxs(ScrollArea, { className: "min-h-0 flex-1", children: [_jsxs("div", { className: "sticky top-0 z-10 border-b border-border bg-popover px-3 py-2 text-xs text-muted-foreground", children: [_jsx("span", { className: "font-medium text-primary tabular-nums", children: value.length }), " ", i18n.columnsSelectedSuffix(value.length)] }), _jsx("div", { className: "flex flex-col gap-2 px-3 py-2 pb-2", children: sections.map((section, si) => {
|
|
22
|
+
const available = section.fields.filter((f) => isFieldAvailableToAddFromCatalog(f, value));
|
|
23
|
+
if (available.length === 0)
|
|
24
|
+
return null;
|
|
25
|
+
const flatUngrouped = !section.groupLabel && available.length === 1;
|
|
26
|
+
if (flatUngrouped) {
|
|
27
|
+
const field = available[0];
|
|
28
|
+
return (_jsx(AddCatalogFieldRow, { field: field, checked: pendingAddIds.has(field.id), onToggle: onTogglePendingAdd, showIcon: true }, field.id));
|
|
29
|
+
}
|
|
30
|
+
const heading = section.groupLabel ??
|
|
31
|
+
(available.length > 1 ? i18n.addColumnsSectionFallbackTitle : (available[0]?.name ?? i18n.addColumnsSectionFallbackTitle));
|
|
32
|
+
return (_jsxs(Collapsible, { defaultOpen: true, className: "flex flex-col gap-1", children: [_jsxs(CollapsibleTrigger, { className: "group flex w-full items-center gap-2 rounded-md px-1 py-1 text-start text-sm font-medium hover:bg-muted/80", type: "button", children: [available[0]?.icon, _jsx("span", { className: "min-w-0 flex-1 truncate", children: heading }), _jsx(ChevronRight, { className: "size-4 shrink-0 text-muted-foreground transition-transform group-data-[panel-open]:rotate-90" })] }), _jsx(CollapsibleContent, { className: "flex flex-col", children: available.map((field) => (_jsx(AddCatalogFieldRow, { field: field, checked: pendingAddIds.has(field.id), onToggle: onTogglePendingAdd }, field.id))) })] }, si));
|
|
33
|
+
}) })] }), _jsxs("div", { className: "flex shrink-0 items-center justify-end gap-2 border-t border-border px-3 py-2.5", children: [_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: onCancel, children: i18n.cancel }), _jsx(Button, { type: "button", size: "sm", disabled: pendingAddIds.size === 0, onClick: onConfirm, children: i18n.addSelectedColumns })] })] }));
|
|
34
|
+
}
|
|
@@ -6,6 +6,11 @@ export interface ColumnDefinition {
|
|
|
6
6
|
id: string;
|
|
7
7
|
name: string;
|
|
8
8
|
description?: string;
|
|
9
|
+
/**
|
|
10
|
+
* When true, the field can appear multiple times in the selection (e.g. different date windows).
|
|
11
|
+
* Default: same as `supportsDateRange` (date-metric columns allow duplicates unless set to `false`).
|
|
12
|
+
*/
|
|
13
|
+
allowMultipleInstances?: boolean;
|
|
9
14
|
/** When true, selecting the row opens the date configuration pane. */
|
|
10
15
|
supportsDateRange?: boolean;
|
|
11
16
|
/** Merged into DateSelector for this column; overrides `Columns` dateMetricOptions. */
|
|
@@ -19,6 +24,8 @@ export interface ColumnFieldGroup {
|
|
|
19
24
|
export type ColumnsFieldsConfig = (ColumnDefinition | ColumnFieldGroup)[];
|
|
20
25
|
export interface SelectedColumn {
|
|
21
26
|
id: string;
|
|
27
|
+
/** Stable row identity for DnD and updates when multiple rows share the same catalog `id`. */
|
|
28
|
+
instanceId?: string;
|
|
22
29
|
dateRange?: DateSelectorValue;
|
|
23
30
|
/**
|
|
24
31
|
* Pin state when pin controls are shown (`false` unpinned, `true` pinned).
|
|
@@ -29,13 +36,31 @@ export interface SelectedColumn {
|
|
|
29
36
|
export interface ColumnsI18n {
|
|
30
37
|
triggerLabel: string;
|
|
31
38
|
triggerCount: (n: number) => string;
|
|
39
|
+
/** Main popover: title above the selected-column list (typically “Columns”). */
|
|
32
40
|
addColumnsTitle: string;
|
|
33
|
-
|
|
41
|
+
/** Opens the staged add-columns view; also used as the add-step panel title (typically “Add columns”). */
|
|
42
|
+
addColumnsAction: string;
|
|
43
|
+
/** Optional tooltip copy for the manage view header (help icon only when non-empty). */
|
|
44
|
+
manageColumnsExplainer?: string;
|
|
45
|
+
/** Optional tooltip copy for the staged add view header (help icon only when non-empty). */
|
|
46
|
+
addColumnsStagedExplainer?: string;
|
|
34
47
|
clearAll: string;
|
|
35
48
|
/** Shown after the numeric count in the footer (e.g. "columns selected"). */
|
|
36
49
|
columnsSelectedSuffix: (n: number) => string;
|
|
37
50
|
configureDateRangeHint: string;
|
|
38
51
|
done: string;
|
|
52
|
+
/** Shown in the manage view when `value` is empty. */
|
|
53
|
+
noColumnsSelected: string;
|
|
54
|
+
/** Staged add view: confirm (appends checked columns, then returns to manage). */
|
|
55
|
+
addSelectedColumns: string;
|
|
56
|
+
/** Staged add view: discard staged selection and return to manage. */
|
|
57
|
+
cancel: string;
|
|
58
|
+
/** `aria-label` for the header help control that reveals extra guidance in a tooltip. */
|
|
59
|
+
headerSectionHelpAriaLabel: string;
|
|
60
|
+
/**
|
|
61
|
+
* Staged add view: section heading when the catalog section has no `group` label and multiple fields are shown together.
|
|
62
|
+
*/
|
|
63
|
+
addColumnsSectionFallbackTitle: string;
|
|
39
64
|
/** Accessible label for the pin control when the column is unpinned. */
|
|
40
65
|
pinColumn: string;
|
|
41
66
|
/** Accessible label for the pin control when the column is pinned. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"columns-types.d.ts","sourceRoot":"","sources":["../../../src/blocks/columns/columns-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE7E,uHAAuH;AACvH,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,iBAAiB,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC;AAErF,MAAM,WAAW,gBAAgB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uFAAuF;IACvF,iBAAiB,CAAC,EAAE,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACtD,IAAI,CAAC,EAAE,SAAS,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAC9B;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,gBAAgB,GAAG,gBAAgB,CAAC,EAAE,CAAC;AAE1E,MAAM,WAAW,cAAc;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,eAAe,EAAE,MAAM,CAAC;IACxB,
|
|
1
|
+
{"version":3,"file":"columns-types.d.ts","sourceRoot":"","sources":["../../../src/blocks/columns/columns-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE7E,uHAAuH;AACvH,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,iBAAiB,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC;AAErF,MAAM,WAAW,gBAAgB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uFAAuF;IACvF,iBAAiB,CAAC,EAAE,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACtD,IAAI,CAAC,EAAE,SAAS,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAC9B;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,gBAAgB,GAAG,gBAAgB,CAAC,EAAE,CAAC;AAE1E,MAAM,WAAW,cAAc;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,gFAAgF;IAChF,eAAe,EAAE,MAAM,CAAC;IACxB,0GAA0G;IAC1G,gBAAgB,EAAE,MAAM,CAAC;IACzB,wFAAwF;IACxF,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,4FAA4F;IAC5F,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,qBAAqB,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,sBAAsB,EAAE,MAAM,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kFAAkF;IAClF,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,0BAA0B,EAAE,MAAM,CAAC;IACnC;;OAEG;IACH,8BAA8B,EAAE,MAAM,CAAC;IACvC,wEAAwE;IACxE,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,eAAe,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,oBAAoB,EAAE,WAoBlC,CAAC;AAEF,MAAM,WAAW,YAAY;IACzB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC5B,kHAAkH;IAClH,iBAAiB,CAAC,EAAE,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACtD;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB"}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
export const DEFAULT_COLUMNS_I18N = {
|
|
2
2
|
triggerLabel: 'Columns',
|
|
3
3
|
triggerCount: (n) => `${n} column${n === 1 ? '' : 's'}`,
|
|
4
|
-
addColumnsTitle: '
|
|
5
|
-
|
|
4
|
+
addColumnsTitle: 'Columns',
|
|
5
|
+
addColumnsAction: 'Add columns',
|
|
6
|
+
manageColumnsExplainer: 'Reorder, pin, and remove columns. Configure date ranges where available.',
|
|
7
|
+
addColumnsStagedExplainer: 'Pick columns, then Add.',
|
|
6
8
|
clearAll: 'Clear all',
|
|
7
9
|
columnsSelectedSuffix: (n) => (n === 1 ? 'column selected' : 'columns selected'),
|
|
8
10
|
configureDateRangeHint: 'Configure date range for this column',
|
|
9
11
|
done: 'Done',
|
|
12
|
+
noColumnsSelected: 'No columns selected.',
|
|
13
|
+
addSelectedColumns: 'Add',
|
|
14
|
+
cancel: 'Cancel',
|
|
15
|
+
headerSectionHelpAriaLabel: 'Help',
|
|
16
|
+
addColumnsSectionFallbackTitle: 'Columns',
|
|
10
17
|
pinColumn: 'Pin column',
|
|
11
18
|
unpinColumn: 'Unpin column',
|
|
12
19
|
pinnedHeading: 'Pinned',
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { DateSelectorValue } from '../date-selector';
|
|
2
2
|
import type { ColumnDefinition, ColumnsFieldsConfig, SelectedColumn } from './columns-types';
|
|
3
|
+
export declare function getSelectedColumnRowKey(row: SelectedColumn): string;
|
|
4
|
+
export declare function newSelectedColumn(definitionId: string): SelectedColumn;
|
|
5
|
+
/** When `allowMultipleInstances` is unset, date-metric columns default to allowing duplicates. */
|
|
6
|
+
export declare function columnAllowsMultipleInstances(def: ColumnDefinition): boolean;
|
|
3
7
|
/** Result of splitting `value` into pinned (prefix) and unpinned (suffix) partitions. */
|
|
4
8
|
export type SplitPinnedPrefixResult = {
|
|
5
9
|
pinned: SelectedColumn[];
|
|
@@ -20,11 +24,11 @@ export declare function mergePinnedPartitions(pinned: SelectedColumn[], unpinned
|
|
|
20
24
|
*/
|
|
21
25
|
export declare function normalizePinnedColumnOrder(value: SelectedColumn[]): SelectedColumn[];
|
|
22
26
|
/**
|
|
23
|
-
* Toggles `pinned` for the
|
|
27
|
+
* Toggles `pinned` for the row with `rowKey` (see {@link getSelectedColumnRowKey}), moving it to the end of the pinned block
|
|
24
28
|
* when pinning or to the start of the unpinned block when unpinning.
|
|
25
|
-
* Unknown
|
|
29
|
+
* Unknown keys return a shallow copy of `value` unchanged.
|
|
26
30
|
*/
|
|
27
|
-
export declare function toggleColumnPinned(value: SelectedColumn[],
|
|
31
|
+
export declare function toggleColumnPinned(value: SelectedColumn[], rowKey: string): SelectedColumn[];
|
|
28
32
|
/**
|
|
29
33
|
* Applies a reorder from sortable indices; returns `null` when the move is invalid or a no-op.
|
|
30
34
|
* With `lockPinning`, moves across the contiguous pinned / unpinned boundary are rejected.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"columns-utils.d.ts","sourceRoot":"","sources":["../../../src/blocks/columns/columns-utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"columns-utils.d.ts","sourceRoot":"","sources":["../../../src/blocks/columns/columns-utils.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAAoB,mBAAmB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE/G,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CAEnE;AAED,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,cAAc,CAEtE;AAED,kGAAkG;AAClG,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAK5E;AAaD,yFAAyF;AACzF,MAAM,MAAM,uBAAuB,GAAG;IAClC,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC9B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,uBAAuB,CAWlF;AAED,6GAA6G;AAC7G,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,MAAM,CAOlE;AAED,qDAAqD;AACrD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CAE5G;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CAGpF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,EAAE,CAoB5F;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,cAAc,EAAE,GAAG,IAAI,CAatJ;AAED,uGAAuG;AACvG,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CAoDtF;AAMD,wBAAgB,cAAc,CAAC,MAAM,EAAE,mBAAmB,GAAG,gBAAgB,EAAE,CAO9E;AAED,MAAM,MAAM,kBAAkB,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAAC;AAErF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,kBAAkB,EAAE,CAWrF;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAE5E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAS5F"}
|
|
@@ -1,7 +1,29 @@
|
|
|
1
|
-
import { arrayMove } from '@dnd-kit/sortable';
|
|
2
1
|
import { format } from 'date-fns';
|
|
3
2
|
import { DEFAULT_DATE_SELECTOR_I18N } from '../date-selector/date-selector-types';
|
|
4
3
|
import { getRollingSummaryLabel } from '../date-selector/date-selector-value';
|
|
4
|
+
export function getSelectedColumnRowKey(row) {
|
|
5
|
+
return row.instanceId ?? row.id;
|
|
6
|
+
}
|
|
7
|
+
export function newSelectedColumn(definitionId) {
|
|
8
|
+
return { id: definitionId, instanceId: crypto.randomUUID() };
|
|
9
|
+
}
|
|
10
|
+
/** When `allowMultipleInstances` is unset, date-metric columns default to allowing duplicates. */
|
|
11
|
+
export function columnAllowsMultipleInstances(def) {
|
|
12
|
+
if (def.allowMultipleInstances !== undefined) {
|
|
13
|
+
return def.allowMultipleInstances;
|
|
14
|
+
}
|
|
15
|
+
return def.supportsDateRange === true;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Move an array item to a new index (inclusive semantics match `@dnd-kit/sortable` `arrayMove`).
|
|
19
|
+
* Implemented locally so this module stays free of `@dnd-kit/sortable`, whose entry re-exports
|
|
20
|
+
* hooks and cannot run under the React Server Components `react` build.
|
|
21
|
+
*/
|
|
22
|
+
function arrayMove(array, from, to) {
|
|
23
|
+
const next = array.slice();
|
|
24
|
+
next.splice(to < 0 ? next.length + to : to, 0, next.splice(from, 1)[0]);
|
|
25
|
+
return next;
|
|
26
|
+
}
|
|
5
27
|
/**
|
|
6
28
|
* Splits selected columns into pinned vs unpinned rows in display order.
|
|
7
29
|
* Uses `pinned === true` for the pinned partition; all other rows go to unpinned.
|
|
@@ -43,21 +65,21 @@ export function normalizePinnedColumnOrder(value) {
|
|
|
43
65
|
return [...pinned.map((c) => ({ ...c, pinned: true })), ...unpinned.map((c) => ({ ...c, pinned: false }))];
|
|
44
66
|
}
|
|
45
67
|
/**
|
|
46
|
-
* Toggles `pinned` for the
|
|
68
|
+
* Toggles `pinned` for the row with `rowKey` (see {@link getSelectedColumnRowKey}), moving it to the end of the pinned block
|
|
47
69
|
* when pinning or to the start of the unpinned block when unpinning.
|
|
48
|
-
* Unknown
|
|
70
|
+
* Unknown keys return a shallow copy of `value` unchanged.
|
|
49
71
|
*/
|
|
50
|
-
export function toggleColumnPinned(value,
|
|
72
|
+
export function toggleColumnPinned(value, rowKey) {
|
|
51
73
|
const { pinned: p0, unpinned: u0 } = splitPinnedPrefix(value);
|
|
52
74
|
const pinned = [...p0];
|
|
53
75
|
const unpinned = [...u0];
|
|
54
|
-
const pi = pinned.findIndex((c) => c
|
|
76
|
+
const pi = pinned.findIndex((c) => getSelectedColumnRowKey(c) === rowKey);
|
|
55
77
|
if (pi >= 0) {
|
|
56
78
|
const [row] = pinned.splice(pi, 1);
|
|
57
79
|
unpinned.unshift({ ...row, pinned: false });
|
|
58
80
|
return mergePinnedPartitions(pinned, unpinned);
|
|
59
81
|
}
|
|
60
|
-
const ui = unpinned.findIndex((c) => c
|
|
82
|
+
const ui = unpinned.findIndex((c) => getSelectedColumnRowKey(c) === rowKey);
|
|
61
83
|
if (ui >= 0) {
|
|
62
84
|
const [row] = unpinned.splice(ui, 1);
|
|
63
85
|
pinned.push({ ...row, pinned: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"columns.d.ts","sourceRoot":"","sources":["../../../src/blocks/columns/columns.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"columns.d.ts","sourceRoot":"","sources":["../../../src/blocks/columns/columns.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAA4B,YAAY,EAAkB,MAAM,iBAAiB,CAAC;AA6B9F,wBAAgB,OAAO,CAAC,EACpB,OAAO,EACP,KAAK,EACL,QAAQ,EACR,SAAS,EACT,uBAAuB,EACvB,IAAI,EAAE,WAAW,EACjB,iBAAiB,EAAE,qBAAqB,EACxC,WAAmB,GACtB,EAAE,YAAY,2CAwWd"}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useMemo, useState } from 'react';
|
|
4
4
|
import { closestCenter } from '@dnd-kit/core';
|
|
5
|
-
import { ChevronDown,
|
|
5
|
+
import { ChevronDown, Columns3, GripVertical, Pin, PinOff, X } from 'lucide-react';
|
|
6
6
|
import { Button } from '../../components/button';
|
|
7
|
-
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../../components/collapsible';
|
|
8
7
|
import { Popover, PopoverContent, PopoverTrigger } from '../../components/popover';
|
|
8
|
+
import { TooltipProvider } from '../../components/tooltip';
|
|
9
9
|
import { ScrollArea } from '../../components/scroll-area';
|
|
10
|
-
import { Separator } from '../../components/separator';
|
|
11
10
|
import { Sortable, SortableItem, SortableItemHandle } from '../../components/sortable';
|
|
12
11
|
import { cn } from '../../lib/utils';
|
|
13
12
|
import { DateSelector } from '../date-selector';
|
|
14
13
|
import { DEFAULT_COLUMNS_I18N } from './columns-types';
|
|
15
|
-
import {
|
|
14
|
+
import { ColumnPopoverPanelHeader } from './column-popover-panel-header';
|
|
15
|
+
import { ColumnsAddView } from './columns-add-view';
|
|
16
|
+
import { buildColumnMap, columnAllowsMultipleInstances, formatColumnDateRangeSecondary, getSelectedColumnRowKey, newSelectedColumn, parseColumnSections, pinnedPrefixLength, reorderColumnsSelection, toggleColumnPinned, } from './columns-utils';
|
|
16
17
|
const DEFAULT_DATE_METRIC_OPTIONS = {
|
|
17
18
|
showRollingPresets: true,
|
|
18
19
|
periodTypes: ['day', 'month', 'quarter', 'year'],
|
|
@@ -28,10 +29,12 @@ export function Columns({ columns, value, onChange, className, popoverContentCla
|
|
|
28
29
|
const columnMap = useMemo(() => buildColumnMap(columns), [columns]);
|
|
29
30
|
const sections = useMemo(() => parseColumnSections(columns), [columns]);
|
|
30
31
|
const [open, setOpen] = useState(false);
|
|
31
|
-
const [
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
32
|
+
const [popoverView, setPopoverView] = useState('manage');
|
|
33
|
+
const [pendingAddIds, setPendingAddIds] = useState(() => new Set());
|
|
34
|
+
const [activeRowKey, setActiveRowKey] = useState(null);
|
|
35
|
+
const activeSelected = activeRowKey ? value.find((s) => getSelectedColumnRowKey(s) === activeRowKey) : undefined;
|
|
36
|
+
const activeDef = activeSelected ? columnMap[activeSelected.id] : undefined;
|
|
37
|
+
const showRightPanel = popoverView === 'manage' && Boolean(activeDef?.supportsDateRange && activeRowKey);
|
|
35
38
|
const mergedDateMetricOptions = useMemo(() => {
|
|
36
39
|
const colOpts = activeDef?.supportsDateRange ? activeDef.dateMetricOptions : undefined;
|
|
37
40
|
return {
|
|
@@ -41,33 +44,73 @@ export function Columns({ columns, value, onChange, className, popoverContentCla
|
|
|
41
44
|
};
|
|
42
45
|
}, [activeDef, dateMetricOptionsProp]);
|
|
43
46
|
const { className: dateMetricClassName, ...dateSelectorProps } = mergedDateMetricOptions;
|
|
44
|
-
const activeSelected = activeColumnId ? value.find((s) => s.id === activeColumnId) : undefined;
|
|
45
47
|
const activeDateRange = activeSelected?.dateRange;
|
|
48
|
+
const resetPopoverUi = useCallback(() => {
|
|
49
|
+
setPopoverView('manage');
|
|
50
|
+
setPendingAddIds(new Set());
|
|
51
|
+
setActiveRowKey(null);
|
|
52
|
+
}, []);
|
|
46
53
|
const handleOpenChange = useCallback((next) => {
|
|
47
54
|
setOpen(next);
|
|
48
55
|
if (!next) {
|
|
49
|
-
|
|
56
|
+
resetPopoverUi();
|
|
50
57
|
}
|
|
51
|
-
}, []);
|
|
52
|
-
const updateSelectedDateRange = useCallback((
|
|
53
|
-
onChange(value.map((row) => (row
|
|
58
|
+
}, [resetPopoverUi]);
|
|
59
|
+
const updateSelectedDateRange = useCallback((rowKey, dateRange) => {
|
|
60
|
+
onChange(value.map((row) => (getSelectedColumnRowKey(row) === rowKey ? { ...row, dateRange } : row)));
|
|
54
61
|
}, [onChange, value]);
|
|
55
|
-
const removeColumn = useCallback((
|
|
56
|
-
onChange(value.filter((v) => v
|
|
57
|
-
|
|
62
|
+
const removeColumn = useCallback((rowKey) => {
|
|
63
|
+
onChange(value.filter((v) => getSelectedColumnRowKey(v) !== rowKey));
|
|
64
|
+
setActiveRowKey((prev) => (prev === rowKey ? null : prev));
|
|
58
65
|
}, [onChange, value]);
|
|
59
66
|
const clearAll = useCallback(() => {
|
|
60
67
|
onChange([]);
|
|
61
|
-
|
|
62
|
-
}, [onChange]);
|
|
63
|
-
const
|
|
64
|
-
|
|
68
|
+
resetPopoverUi();
|
|
69
|
+
}, [onChange, resetPopoverUi]);
|
|
70
|
+
const goToAddView = useCallback(() => {
|
|
71
|
+
setActiveRowKey(null);
|
|
72
|
+
setPendingAddIds(new Set());
|
|
73
|
+
setPopoverView('add');
|
|
74
|
+
}, []);
|
|
75
|
+
const cancelAddView = useCallback(() => {
|
|
76
|
+
setPendingAddIds(new Set());
|
|
77
|
+
setPopoverView('manage');
|
|
78
|
+
}, []);
|
|
79
|
+
const confirmAddColumns = useCallback(() => {
|
|
80
|
+
if (pendingAddIds.size === 0)
|
|
65
81
|
return;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
82
|
+
const idsToAdd = Array.from(pendingAddIds);
|
|
83
|
+
let next = [...value];
|
|
84
|
+
for (const defId of idsToAdd) {
|
|
85
|
+
const def = columnMap[defId];
|
|
86
|
+
if (!def || !columnAllowsMultipleInstances(def))
|
|
87
|
+
continue;
|
|
88
|
+
if (next.some((r) => r.id === defId)) {
|
|
89
|
+
next = next.map((r) => (r.id === defId && r.instanceId === undefined ? { ...r, instanceId: crypto.randomUUID() } : r));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const additions = idsToAdd.map((id) => newSelectedColumn(id));
|
|
93
|
+
onChange([...next, ...additions]);
|
|
94
|
+
setPendingAddIds(new Set());
|
|
95
|
+
setPopoverView('manage');
|
|
96
|
+
}, [onChange, pendingAddIds, value, columnMap]);
|
|
97
|
+
const togglePendingAdd = useCallback((id, checked) => {
|
|
98
|
+
setPendingAddIds((prev) => {
|
|
99
|
+
const next = new Set(prev);
|
|
100
|
+
if (checked)
|
|
101
|
+
next.add(id);
|
|
102
|
+
else
|
|
103
|
+
next.delete(id);
|
|
104
|
+
return next;
|
|
105
|
+
});
|
|
70
106
|
}, []);
|
|
107
|
+
const handleRowActivate = useCallback((rowKey) => {
|
|
108
|
+
const row = value.find((s) => getSelectedColumnRowKey(s) === rowKey);
|
|
109
|
+
const def = row ? columnMap[row.id] : undefined;
|
|
110
|
+
if (!def?.supportsDateRange)
|
|
111
|
+
return;
|
|
112
|
+
setActiveRowKey(rowKey);
|
|
113
|
+
}, [value, columnMap]);
|
|
71
114
|
const pinnedPrefixLen = useMemo(() => pinnedPrefixLength(value), [value]);
|
|
72
115
|
const unpinnedCount = value.length - pinnedPrefixLen;
|
|
73
116
|
const lockedPartitionCollision = useMemo(() => {
|
|
@@ -76,12 +119,12 @@ export function Columns({ columns, value, onChange, className, popoverContentCla
|
|
|
76
119
|
return (args) => {
|
|
77
120
|
const collisions = closestCenter(args);
|
|
78
121
|
const pc = pinnedPrefixLength(value);
|
|
79
|
-
const activeIdx = value.findIndex((item) => item
|
|
122
|
+
const activeIdx = value.findIndex((item) => getSelectedColumnRowKey(item) === String(args.active.id));
|
|
80
123
|
if (activeIdx < 0 || !collisions?.length)
|
|
81
124
|
return collisions;
|
|
82
125
|
const activeInPinned = activeIdx < pc;
|
|
83
126
|
const filtered = collisions.filter((c) => {
|
|
84
|
-
const overIdx = value.findIndex((item) => item
|
|
127
|
+
const overIdx = value.findIndex((item) => getSelectedColumnRowKey(item) === String(c.id));
|
|
85
128
|
if (overIdx < 0)
|
|
86
129
|
return false;
|
|
87
130
|
return overIdx < pc === activeInPinned;
|
|
@@ -89,8 +132,8 @@ export function Columns({ columns, value, onChange, className, popoverContentCla
|
|
|
89
132
|
return filtered;
|
|
90
133
|
};
|
|
91
134
|
}, [lockPinning, value]);
|
|
92
|
-
const handleTogglePin = useCallback((
|
|
93
|
-
onChange(toggleColumnPinned(value,
|
|
135
|
+
const handleTogglePin = useCallback((rowKey) => {
|
|
136
|
+
onChange(toggleColumnPinned(value, rowKey));
|
|
94
137
|
}, [onChange, value]);
|
|
95
138
|
const handleColumnsMove = useCallback(({ activeIndex, overIndex }) => {
|
|
96
139
|
const next = reorderColumnsSelection(value, lockPinning, activeIndex, overIndex);
|
|
@@ -98,43 +141,38 @@ export function Columns({ columns, value, onChange, className, popoverContentCla
|
|
|
98
141
|
onChange(next);
|
|
99
142
|
}, [value, onChange, lockPinning]);
|
|
100
143
|
const count = value.length;
|
|
101
|
-
return (_jsx("div", { className: cn(className), children: _jsxs(Popover, { open: open, onOpenChange: handleOpenChange, children: [_jsxs(PopoverTrigger, { render: _jsx(Button, { type: "button", variant: "outline", size: "sm", className: "gap-2 font-normal", "aria-expanded": open, "aria-haspopup": "dialog" }), children: [_jsx(Columns3, { className: "
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return (_jsxs("button", { type: "button", className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-start text-sm text-foreground hover:bg-accent hover:text-accent-foreground", onClick: () => addColumn(field.id), children: [field.icon, _jsx("span", { className: "min-w-0 flex-1 truncate", children: field.name })] }, field.id));
|
|
136
|
-
}
|
|
137
|
-
const heading = section.groupLabel ?? (available.length > 1 ? 'Columns' : (available[0]?.name ?? 'Columns'));
|
|
138
|
-
return (_jsxs(Collapsible, { defaultOpen: true, className: "flex flex-col gap-1", children: [_jsxs(CollapsibleTrigger, { className: "group flex w-full items-center gap-2 rounded-md px-1 py-1.5 text-start text-sm font-medium hover:bg-muted/80", type: "button", children: [available[0]?.icon, _jsx("span", { className: "min-w-0 flex-1 truncate", children: heading }), _jsx(ChevronRight, { className: "size-4 shrink-0 text-muted-foreground transition-transform group-data-[panel-open]:rotate-90" })] }), _jsx(CollapsibleContent, { className: "flex flex-col gap-0.5 ps-1", children: available.map((field) => (_jsx("button", { type: "button", className: "rounded-md px-2 py-1.5 text-start text-sm text-foreground hover:bg-accent hover:text-accent-foreground", onClick: () => addColumn(field.id), children: field.name }, field.id))) })] }, si));
|
|
139
|
-
}) })] }) }), _jsxs("div", { className: "shrink-0 border-t border-border px-3 py-2 text-xs text-muted-foreground", children: [_jsx("span", { className: "font-medium text-primary tabular-nums", children: count }), " ", i18n.columnsSelectedSuffix(count)] })] }), showRightPanel && activeColumnId && activeDef?.supportsDateRange ? (_jsxs("div", { className: "flex min-h-0 w-[min(100%,380px)] min-w-[280px] flex-1 flex-col border-border bg-popover", children: [_jsxs("div", { className: "flex shrink-0 items-start justify-between gap-2 border-b border-border px-3 py-2.5", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium", children: activeDef.name }), _jsx("p", { className: "text-xs text-muted-foreground", children: activeDef.description ?? i18n.configureDateRangeHint })] }), _jsx("button", { type: "button", className: "shrink-0 rounded-sm p-1 text-muted-foreground hover:bg-muted hover:text-foreground", "aria-label": "Close configuration", onClick: () => setActiveColumnId(null), children: _jsx(X, { className: "size-4" }) })] }), _jsx("div", { className: "flex min-h-0 flex-1 flex-col overflow-hidden", children: _jsx("div", { className: "flex min-h-0 flex-1 flex-col p-3", children: _jsx(DateSelector, { ...dateSelectorProps, value: activeDateRange, onChange: (next) => updateSelectedDateRange(activeColumnId, next), className: cn('min-h-0 max-w-none flex-1', dateMetricClassName) }) }) }), _jsx("div", { className: "flex shrink-0 items-center justify-end gap-2 border-t border-border px-3 py-2.5", children: _jsx(Button, { type: "button", size: "sm", onClick: () => setActiveColumnId(null), children: i18n.done }) })] })) : null] }) })] }) }));
|
|
144
|
+
return (_jsx("div", { className: cn(className), children: _jsxs(Popover, { open: open, onOpenChange: handleOpenChange, children: [_jsxs(PopoverTrigger, { render: _jsx(Button, { type: "button", variant: "outline", size: "sm", className: "gap-2 font-normal", "aria-expanded": open, "aria-haspopup": "dialog" }), children: [_jsx(Columns3, { className: "text-primary", "aria-hidden": true }), _jsxs("span", { className: "text-sm", children: [i18n.triggerLabel, " \u00B7 ", i18n.triggerCount(count)] }), _jsx(ChevronDown, { className: "opacity-60", "aria-hidden": true })] }), _jsx(PopoverContent, { align: "start", className: cn('w-auto max-w-[min(100vw-2rem,640px)] gap-0 overflow-hidden p-0 shadow-lg ring-1 ring-foreground/10', showRightPanel ? 'min-w-[min(100vw-2rem,640px)]' : 'min-w-[min(100vw-2rem,320px)]', popoverContentClassName), children: _jsx(TooltipProvider, { delay: 200, children: _jsxs("div", { className: "flex max-h-[min(85vh,560px)] min-h-0", children: [_jsx("div", { className: "flex min-h-0 w-[320px] shrink-0 flex-col", children: popoverView === 'manage' ? (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [_jsx(ColumnPopoverPanelHeader, { title: i18n.addColumnsTitle, explainer: i18n.manageColumnsExplainer, trailing: _jsx("button", { type: "button", onClick: clearAll, disabled: count === 0, className: "shrink-0 text-xs font-medium text-primary hover:underline disabled:pointer-events-none disabled:opacity-40", children: i18n.clearAll }) }), _jsxs(ScrollArea, { className: "min-h-0 flex-1", children: [_jsxs("div", { className: "sticky top-0 z-10 border-b border-border bg-popover px-3 py-2 text-xs text-muted-foreground", children: [_jsx("span", { className: "font-medium text-primary tabular-nums", children: count }), " ", i18n.columnsSelectedSuffix(count)] }), _jsx("div", { className: "flex flex-col gap-3", children: value.length > 0 ? (_jsxs("div", { className: "flex flex-col gap-2", children: [lockPinning && pinnedPrefixLen > 0 ? (_jsx("p", { className: "px-0.5 text-xs font-medium text-muted-foreground", children: i18n.pinnedHeading })) : null, _jsx(Sortable, { value: value, onValueChange: onChange, getItemValue: (item) => getSelectedColumnRowKey(item), onMove: handleColumnsMove, collisionDetection: lockedPartitionCollision, className: "flex flex-col divide-y divide-border", children: value.flatMap((row, index) => {
|
|
145
|
+
const def = columnMap[row.id];
|
|
146
|
+
if (!def)
|
|
147
|
+
return [];
|
|
148
|
+
const rowKey = getSelectedColumnRowKey(row);
|
|
149
|
+
const secondary = row.dateRange && def.supportsDateRange
|
|
150
|
+
? formatColumnDateRangeSecondary(row.dateRange)
|
|
151
|
+
: null;
|
|
152
|
+
const showHighlight = activeRowKey === rowKey && Boolean(def.supportsDateRange);
|
|
153
|
+
const isPinned = row.pinned === true;
|
|
154
|
+
const showSectionDivider = lockPinning && pinnedPrefixLen > 0 && unpinnedCount > 0 && index === pinnedPrefixLen;
|
|
155
|
+
const iconBtnClass = cn('shrink-0 rounded-sm p-0.5 outline-none hover:bg-black/5 focus-visible:ring-2 focus-visible:ring-ring hover:[&_svg]:opacity-100', showHighlight && 'hover:bg-white/15');
|
|
156
|
+
const nodes = [];
|
|
157
|
+
if (showSectionDivider) {
|
|
158
|
+
nodes.push(_jsx("p", { className: "px-3 py-2 text-xs font-medium text-muted-foreground", children: i18n.unpinnedHeading }, `columns-unpinned-heading-${rowKey}`));
|
|
159
|
+
}
|
|
160
|
+
nodes.push(_jsxs(SortableItem, { value: rowKey, className: cn('flex min-w-0 items-center gap-1 px-2 py-2 text-sm transition-colors', showHighlight
|
|
161
|
+
? 'border-primary bg-primary text-primary-foreground'
|
|
162
|
+
: 'border-border bg-card text-card-foreground', def.supportsDateRange && !showHighlight && 'hover:bg-accent'), children: [_jsx(SortableItemHandle, { className: cn('shrink-0 touch-none', showHighlight ? 'text-primary-foreground/80' : 'text-muted-foreground'), "aria-label": `Reorder ${def.name}`, children: _jsx(GripVertical, {}) }), _jsx(ColumnRowLabel, { name: def.name, secondary: secondary, showHighlight: showHighlight, activatable: Boolean(def.supportsDateRange), onActivate: def.supportsDateRange ? () => handleRowActivate(rowKey) : undefined }), !lockPinning && row.pinned !== undefined ? (_jsx("button", { type: "button", className: iconBtnClass, "aria-pressed": isPinned, "aria-label": isPinned ? i18n.unpinColumn : i18n.pinColumn, onClick: (e) => {
|
|
163
|
+
e.stopPropagation();
|
|
164
|
+
handleTogglePin(rowKey);
|
|
165
|
+
}, children: isPinned ? (_jsx(PinOff, { className: "size-4", "aria-hidden": true })) : (_jsx(Pin, { className: "size-4 opacity-30", "aria-hidden": true })) })) : null, _jsx("button", { type: "button", className: cn(iconBtnClass, '[&_svg]:opacity-30'), "aria-label": `Remove ${def.name}`, onClick: (e) => {
|
|
166
|
+
e.stopPropagation();
|
|
167
|
+
removeColumn(rowKey);
|
|
168
|
+
}, children: _jsx(X, {}) })] }, rowKey));
|
|
169
|
+
return nodes;
|
|
170
|
+
}) })] })) : (_jsx("p", { className: "text-xs text-muted-foreground", children: i18n.noColumnsSelected })) })] }), _jsx("div", { className: "shrink-0 border-t border-border bg-popover p-3", children: _jsx(Button, { type: "button", variant: "outline", size: "sm", className: "w-full", onClick: goToAddView, children: i18n.addColumnsAction }) })] })) : (_jsx(ColumnsAddView, { i18n: i18n, sections: sections, value: value, pendingAddIds: pendingAddIds, onTogglePendingAdd: togglePendingAdd, onCancel: cancelAddView, onConfirm: confirmAddColumns })) }), showRightPanel && activeDef && activeRowKey ? (_jsxs("div", { className: "flex min-h-0 w-[min(100%,380px)] min-w-[280px] flex-1 flex-col border-l border-border bg-popover", children: [_jsx(ColumnPopoverPanelHeader, { title: activeDef.name, explainer: activeDef.description ?? i18n.configureDateRangeHint, helpAriaLabel: i18n.headerSectionHelpAriaLabel, trailing: _jsx("button", { type: "button", className: "shrink-0 rounded-sm p-1 text-muted-foreground hover:bg-muted hover:text-foreground", "aria-label": "Close configuration", onClick: () => setActiveRowKey(null), children: _jsx(X, {}) }) }), _jsx("div", { className: "flex min-h-0 flex-1 flex-col overflow-hidden p-3", children: _jsx(DateSelector, { ...dateSelectorProps, value: activeDateRange, onChange: (next) => updateSelectedDateRange(activeRowKey, next), className: cn('min-h-0 max-w-none flex-1', dateMetricClassName) }) })] })) : null] }) }) })] }) }));
|
|
171
|
+
}
|
|
172
|
+
function ColumnRowLabel({ name, secondary, showHighlight, activatable, onActivate, }) {
|
|
173
|
+
const label = (_jsxs(_Fragment, { children: [_jsx("span", { className: cn('truncate font-medium', !secondary && 'flex-1'), children: name }), secondary ? (_jsxs("span", { className: cn('truncate text-xs', showHighlight ? 'text-primary-foreground/85' : 'text-muted-foreground'), children: ["\u00B7 ", secondary] })) : null] }));
|
|
174
|
+
if (activatable) {
|
|
175
|
+
return (_jsx("button", { type: "button", className: "flex min-w-0 flex-1 items-center gap-1.5 text-start", onClick: onActivate, children: label }));
|
|
176
|
+
}
|
|
177
|
+
return _jsx("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: label });
|
|
140
178
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"columns.stories.d.ts","sourceRoot":"","sources":["../../../src/blocks/columns/columns.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"columns.stories.d.ts","sourceRoot":"","sources":["../../../src/blocks/columns/columns.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAgHvD,QAAA,MAAM,IAAI;;;;;;CAMM,CAAC;AAEjB,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,KAE3B,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAEnB,CAAC"}
|
|
@@ -2,11 +2,17 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { Activity, BarChart3, Home, Users } from 'lucide-react';
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { Columns } from './columns';
|
|
5
|
-
const
|
|
5
|
+
const rolling30 = {
|
|
6
6
|
period: 'day',
|
|
7
7
|
operator: 'between',
|
|
8
8
|
selectionMode: 'rolling-next',
|
|
9
|
-
rollingDays:
|
|
9
|
+
rollingDays: 30,
|
|
10
|
+
};
|
|
11
|
+
const rolling60 = {
|
|
12
|
+
period: 'day',
|
|
13
|
+
operator: 'between',
|
|
14
|
+
selectionMode: 'rolling-next',
|
|
15
|
+
rollingDays: 60,
|
|
10
16
|
};
|
|
11
17
|
const catalog = [
|
|
12
18
|
{
|
|
@@ -52,7 +58,8 @@ const catalog = [
|
|
|
52
58
|
const initialSelected = [
|
|
53
59
|
{ id: 'bedrooms', pinned: false },
|
|
54
60
|
{ id: 'bathrooms', pinned: false },
|
|
55
|
-
{ id: 'occupancy', dateRange:
|
|
61
|
+
{ id: 'occupancy', instanceId: 'occ-30', dateRange: rolling30, pinned: false },
|
|
62
|
+
{ id: 'occupancy', instanceId: 'occ-60', dateRange: rolling60, pinned: false },
|
|
56
63
|
{ id: 'adr', pinned: false },
|
|
57
64
|
];
|
|
58
65
|
/** Pinned-first order: pinned prefix, then unpinned. */
|
|
@@ -60,7 +67,8 @@ const initialPinningLocked = [
|
|
|
60
67
|
{ id: 'bedrooms', pinned: true },
|
|
61
68
|
{ id: 'address', pinned: true },
|
|
62
69
|
{ id: 'bathrooms' },
|
|
63
|
-
{ id: 'occupancy', dateRange:
|
|
70
|
+
{ id: 'occupancy', instanceId: 'occ-30', dateRange: rolling30 },
|
|
71
|
+
{ id: 'occupancy', instanceId: 'occ-60', dateRange: rolling60 },
|
|
64
72
|
{ id: 'adr' },
|
|
65
73
|
];
|
|
66
74
|
function ColumnsPlayground() {
|
|
@@ -71,6 +79,10 @@ function ColumnsPinningLockedPlayground() {
|
|
|
71
79
|
const [value, setValue] = useState(initialPinningLocked);
|
|
72
80
|
return (_jsx("div", { className: "p-6", children: _jsx(Columns, { columns: catalog, value: value, onChange: setValue, lockPinning: true }) }));
|
|
73
81
|
}
|
|
82
|
+
function ColumnsEmptyPlayground() {
|
|
83
|
+
const [value, setValue] = useState([]);
|
|
84
|
+
return (_jsx("div", { className: "p-6", children: _jsx(Columns, { columns: catalog, value: value, onChange: setValue }) }));
|
|
85
|
+
}
|
|
74
86
|
const meta = {
|
|
75
87
|
title: 'Blocks/Columns',
|
|
76
88
|
tags: ['autodocs'],
|
|
@@ -85,3 +97,6 @@ export const Default = {
|
|
|
85
97
|
export const PinningLocked = {
|
|
86
98
|
render: () => _jsx(ColumnsPinningLockedPlayground, {}),
|
|
87
99
|
};
|
|
100
|
+
export const Empty = {
|
|
101
|
+
render: () => _jsx(ColumnsEmptyPlayground, {}),
|
|
102
|
+
};
|