@cratis/components 0.1.9 → 0.1.10

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.
Files changed (101) hide show
  1. package/dist/cjs/PivotViewer/PivotViewer.css +1258 -0
  2. package/dist/cjs/PivotViewer/components/Spinner.css +77 -0
  3. package/dist/cjs/TimeMachine/EventsView.css +213 -0
  4. package/dist/cjs/TimeMachine/TimeMachine.css +567 -0
  5. package/dist/esm/PivotViewer/PivotViewer.css +1258 -0
  6. package/dist/esm/PivotViewer/components/Spinner.css +77 -0
  7. package/dist/esm/TimeMachine/EventsView.css +213 -0
  8. package/dist/esm/TimeMachine/TimeMachine.css +567 -0
  9. package/package.json +3 -4
  10. package/.storybook/main.ts +0 -24
  11. package/CommandDialog/CommandDialog.stories.tsx +0 -25
  12. package/CommandDialog/CommandDialog.tsx +0 -161
  13. package/CommandDialog/index.ts +0 -4
  14. package/CommandForm/CommandForm.stories.tsx +0 -24
  15. package/CommandForm/CommandForm.tsx +0 -266
  16. package/CommandForm/CommandFormField.tsx +0 -27
  17. package/CommandForm/CommandFormFields.tsx +0 -142
  18. package/CommandForm/DatePickerField.tsx +0 -57
  19. package/CommandForm/DropdownField.tsx +0 -65
  20. package/CommandForm/InputTextField.tsx +0 -62
  21. package/CommandForm/SliderField.tsx +0 -68
  22. package/CommandForm/index.ts +0 -10
  23. package/Common/ErrorBoundary.stories.tsx +0 -10
  24. package/Common/ErrorBoundary.tsx +0 -41
  25. package/Common/FormElement.stories.tsx +0 -10
  26. package/Common/FormElement.tsx +0 -20
  27. package/Common/Page.stories.tsx +0 -10
  28. package/Common/Page.tsx +0 -21
  29. package/Common/index.ts +0 -6
  30. package/DataPage/DataPage.stories.tsx +0 -10
  31. package/DataPage/DataPage.tsx +0 -191
  32. package/DataPage/index.ts +0 -4
  33. package/DataTables/DataTableForObservableQuery.stories.tsx +0 -10
  34. package/DataTables/DataTableForObservableQuery.tsx +0 -97
  35. package/DataTables/DataTableForQuery.stories.tsx +0 -10
  36. package/DataTables/DataTableForQuery.tsx +0 -97
  37. package/DataTables/index.ts +0 -5
  38. package/Dialogs/BusyIndicatorDialog.stories.tsx +0 -26
  39. package/Dialogs/BusyIndicatorDialog.tsx +0 -26
  40. package/Dialogs/ConfirmationDialog.stories.tsx +0 -36
  41. package/Dialogs/ConfirmationDialog.tsx +0 -75
  42. package/Dialogs/index.ts +0 -5
  43. package/Dropdown/Dropdown.tsx +0 -23
  44. package/Dropdown/index.ts +0 -4
  45. package/PivotViewer/PivotViewer.stories.tsx +0 -24
  46. package/PivotViewer/PivotViewer.tsx +0 -791
  47. package/PivotViewer/components/AxisLabels.tsx +0 -69
  48. package/PivotViewer/components/DetailPanel.tsx +0 -108
  49. package/PivotViewer/components/FilterPanel.tsx +0 -189
  50. package/PivotViewer/components/FilterPanelContainer.tsx +0 -10
  51. package/PivotViewer/components/PivotCanvas.tsx +0 -660
  52. package/PivotViewer/components/PivotViewerMain.tsx +0 -229
  53. package/PivotViewer/components/RangeHistogramFilter.tsx +0 -220
  54. package/PivotViewer/components/Spinner.tsx +0 -21
  55. package/PivotViewer/components/Toolbar.tsx +0 -130
  56. package/PivotViewer/components/ToolbarContainer.tsx +0 -10
  57. package/PivotViewer/components/index.ts +0 -12
  58. package/PivotViewer/components/pivot/animation.ts +0 -108
  59. package/PivotViewer/components/pivot/buckets.ts +0 -152
  60. package/PivotViewer/components/pivot/colorResolver.ts +0 -67
  61. package/PivotViewer/components/pivot/constants.ts +0 -46
  62. package/PivotViewer/components/pivot/sprites.ts +0 -265
  63. package/PivotViewer/components/pivot/visibility.ts +0 -319
  64. package/PivotViewer/constants.ts +0 -9
  65. package/PivotViewer/engine/layout.ts +0 -149
  66. package/PivotViewer/engine/pivot.worker.ts +0 -86
  67. package/PivotViewer/engine/store.ts +0 -437
  68. package/PivotViewer/engine/types.ts +0 -255
  69. package/PivotViewer/hooks/index.ts +0 -13
  70. package/PivotViewer/hooks/useContainerDimensions.ts +0 -45
  71. package/PivotViewer/hooks/useDimensionState.ts +0 -53
  72. package/PivotViewer/hooks/useFilterOptions.ts +0 -36
  73. package/PivotViewer/hooks/useFilterPanelDrag.ts +0 -49
  74. package/PivotViewer/hooks/useFilterState.ts +0 -106
  75. package/PivotViewer/hooks/useFilteredData.ts +0 -119
  76. package/PivotViewer/hooks/usePanning.ts +0 -163
  77. package/PivotViewer/hooks/usePivotEngine.ts +0 -252
  78. package/PivotViewer/hooks/useSelectedItem.ts +0 -402
  79. package/PivotViewer/hooks/useWheelZoom.ts +0 -114
  80. package/PivotViewer/hooks/useZoomState.ts +0 -34
  81. package/PivotViewer/index.ts +0 -7
  82. package/PivotViewer/types.ts +0 -59
  83. package/PivotViewer/utils/animations.ts +0 -249
  84. package/PivotViewer/utils/constants.ts +0 -20
  85. package/PivotViewer/utils/index.ts +0 -6
  86. package/PivotViewer/utils/selection.ts +0 -292
  87. package/PivotViewer/utils/utils.ts +0 -259
  88. package/TimeMachine/EventsView.stories.tsx +0 -10
  89. package/TimeMachine/EventsView.tsx +0 -119
  90. package/TimeMachine/Properties.stories.tsx +0 -10
  91. package/TimeMachine/Properties.tsx +0 -98
  92. package/TimeMachine/ReadModelView.stories.tsx +0 -10
  93. package/TimeMachine/ReadModelView.tsx +0 -143
  94. package/TimeMachine/TimeMachine.stories.tsx +0 -10
  95. package/TimeMachine/TimeMachine.tsx +0 -244
  96. package/TimeMachine/index.ts +0 -8
  97. package/TimeMachine/types.ts +0 -23
  98. package/global.d.ts +0 -11
  99. package/index.ts +0 -22
  100. package/useOverlayZIndex.ts +0 -32
  101. package/vite.config.ts +0 -80
@@ -1,229 +0,0 @@
1
- // Copyright (c) Cratis. All rights reserved.
2
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
-
4
- import type { ReactNode } from 'react';
5
- import type { ItemId, LayoutResult, GroupingResult } from '../engine/types';
6
- import type { ViewMode } from './Toolbar';
7
- import type { PivotDimensionFilter } from '../hooks/useDimensionState';
8
- import { Spinner } from './Spinner';
9
- import { PivotCanvas } from './PivotCanvas';
10
- import { AxisLabels } from './AxisLabels';
11
- import { DetailPanel } from './DetailPanel';
12
-
13
- export interface PivotViewerMainProps<TItem extends object> {
14
- data: TItem[];
15
- ready: boolean;
16
- isLoading: boolean;
17
- visibleIds: Uint32Array;
18
- grouping: GroupingResult;
19
- layout: LayoutResult;
20
- cardWidth: number;
21
- cardHeight: number;
22
- zoomLevel: number;
23
- scrollPosition: { x: number; y: number };
24
- containerDimensions: { width: number; height: number };
25
- selectedItem: TItem | null;
26
- hoveredGroupIndex: number | null;
27
- isZooming: boolean;
28
- viewMode: ViewMode;
29
- cardRenderer?: (item: TItem) => ReactNode;
30
- resolveId: (item: TItem, index: number) => ItemId;
31
- emptyContent?: ReactNode;
32
- dimensionFilter: PivotDimensionFilter;
33
- onCardClick: (item: TItem, e: MouseEvent, id: number | string) => void;
34
- onPanStart: (e: React.MouseEvent) => void;
35
- onPanMove: (e: React.MouseEvent) => void;
36
- onPanEnd: () => void;
37
- onGroupHover: (index: number | null) => void;
38
- onAxisLabelClick: (value: string) => void;
39
- onCloseDetail: () => void;
40
- containerRef: React.RefObject<HTMLDivElement | null>;
41
- axisLabelsRef: React.RefObject<HTMLDivElement | null>;
42
- spacerRef: React.RefObject<HTMLDivElement | null>;
43
- }
44
-
45
- export function PivotViewerMain<TItem extends object>({
46
- data,
47
- ready,
48
- isLoading,
49
- visibleIds,
50
- grouping,
51
- layout,
52
- cardWidth,
53
- cardHeight,
54
- zoomLevel,
55
- scrollPosition,
56
- containerDimensions,
57
- selectedItem,
58
- hoveredGroupIndex,
59
- isZooming,
60
- viewMode,
61
- cardRenderer,
62
- resolveId,
63
- emptyContent,
64
- dimensionFilter,
65
- onCardClick,
66
- onPanStart,
67
- onPanMove,
68
- onPanEnd,
69
- onGroupHover,
70
- onAxisLabelClick,
71
- onCloseDetail,
72
- containerRef,
73
- axisLabelsRef,
74
- spacerRef,
75
- }: PivotViewerMainProps<TItem>) {
76
- const handleViewportClick = (e: React.MouseEvent) => {
77
- if (isZooming || !containerRef.current) return;
78
-
79
- const container = containerRef.current;
80
- const rect = container.getBoundingClientRect();
81
- // Use live DOM scroll position for accurate hit testing
82
- const scrollLeft = container.scrollLeft;
83
- const scrollTop = container.scrollTop;
84
-
85
- const clickX = e.clientX - rect.left + scrollLeft;
86
- const clickY = e.clientY - rect.top + scrollTop;
87
-
88
- const worldX = clickX / zoomLevel;
89
- const worldY = clickY / zoomLevel;
90
-
91
- // Check visible items
92
- for (let i = 0; i < visibleIds.length; i++) {
93
- const id = visibleIds[i];
94
- const pos = layout.positions.get(id);
95
- if (pos) {
96
- if (worldX >= pos.x && worldX <= pos.x + cardWidth &&
97
- worldY >= pos.y && worldY <= pos.y + cardHeight) {
98
- const item = data[id];
99
- if (item) {
100
- onCardClick(item, e.nativeEvent as unknown as MouseEvent, id);
101
- }
102
- return;
103
- }
104
- }
105
- }
106
- };
107
-
108
- const handleViewportMouseMove = (e: React.MouseEvent) => {
109
- if (isZooming || !containerRef.current) return;
110
-
111
- const container = containerRef.current;
112
- const rect = container.getBoundingClientRect();
113
- const scrollLeft = container.scrollLeft;
114
- const scrollTop = container.scrollTop;
115
-
116
- const mouseX = e.clientX - rect.left + scrollLeft;
117
- const mouseY = e.clientY - rect.top + scrollTop;
118
-
119
- const worldX = mouseX / zoomLevel;
120
- const worldY = mouseY / zoomLevel;
121
-
122
- let isOverCard = false;
123
- for (let i = 0; i < visibleIds.length; i++) {
124
- const id = visibleIds[i];
125
- const pos = layout.positions.get(id);
126
- if (pos) {
127
- if (worldX >= pos.x && worldX <= pos.x + cardWidth &&
128
- worldY >= pos.y && worldY <= pos.y + cardHeight) {
129
- isOverCard = true;
130
- break;
131
- }
132
- }
133
- }
134
-
135
- container.style.cursor = isOverCard ? 'pointer' : 'default';
136
- };
137
-
138
- return isLoading ? (
139
- <Spinner />
140
- ) : (
141
- <div className="pv-groups-wrapper">
142
- <div style={{ position: 'relative', flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
143
- <div
144
- className={`pv-viewport ${isZooming ? 'pv-zooming' : ''}`}
145
- ref={containerRef}
146
- style={{ overflow: 'auto', position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, zIndex: 1 }}
147
- onClick={handleViewportClick}
148
- onMouseMove={handleViewportMouseMove}
149
- >
150
- {/* Spacer for scrolling - explicitly rendered to allow synchronous updates during animation */}
151
- <div
152
- ref={spacerRef}
153
- style={{
154
- position: 'absolute',
155
- top: 0,
156
- left: 0,
157
- width: layout.totalWidth * zoomLevel,
158
- height: layout.totalHeight * zoomLevel,
159
- pointerEvents: 'none'
160
- }}
161
- />
162
-
163
- {!ready && (
164
- <div className="pv-loading">Building indexes...</div>
165
- )}
166
-
167
- {ready && visibleIds.length === 0 && (
168
- <div className="pv-empty">
169
- {emptyContent ?? 'No items to display.'}
170
- </div>
171
- )}
172
-
173
- {ready && visibleIds.length > 0 && (
174
- <PivotCanvas
175
- items={data}
176
- layout={layout}
177
- grouping={grouping}
178
- visibleIds={visibleIds}
179
- cardWidth={cardWidth}
180
- cardHeight={cardHeight}
181
- zoomLevel={zoomLevel}
182
- panX={scrollPosition.x}
183
- panY={scrollPosition.y}
184
- viewportWidth={containerDimensions.width}
185
- viewportHeight={containerDimensions.height}
186
- selectedId={selectedItem ? resolveId(selectedItem, 0) : null}
187
- hoveredGroupIndex={hoveredGroupIndex}
188
- isZooming={isZooming}
189
- cardRenderer={cardRenderer}
190
- resolveId={resolveId}
191
- onCardClick={onCardClick}
192
- onPanStart={onPanStart as any}
193
- onPanMove={onPanMove as any}
194
- onPanEnd={onPanEnd}
195
- containerRef={containerRef}
196
- viewMode={viewMode}
197
- />
198
- )}
199
- </div>
200
- <DetailPanel
201
- selectedItem={selectedItem}
202
- onClose={onCloseDetail}
203
- />
204
- </div>
205
-
206
- {viewMode === 'grouped' && grouping.groups.length > 0 && (
207
- <AxisLabels
208
- groups={grouping.groups.map((g) => ({
209
- key: g.key,
210
- value: g.value,
211
- label: String(g.value),
212
- items: [],
213
- count: g.ids.length,
214
- }))}
215
- bucketWidths={layout.bucketWidths || []}
216
- zoomLevel={zoomLevel}
217
- dimensionFilter={dimensionFilter}
218
- hoveredGroup={hoveredGroupIndex !== null ? String(grouping.groups[hoveredGroupIndex]?.value) : null}
219
- onHover={(label) => {
220
- const index = grouping.groups.findIndex(g => String(g.value) === label);
221
- onGroupHover(index >= 0 ? index : null);
222
- }}
223
- onClick={onAxisLabelClick}
224
- containerRef={axisLabelsRef}
225
- />
226
- )}
227
- </div>
228
- );
229
- }
@@ -1,220 +0,0 @@
1
- // Copyright (c) Cratis. All rights reserved.
2
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
-
4
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
- import type { PivotPrimitive } from '../types';
6
-
7
- export interface RangeHistogramFilterProps {
8
- values: PivotPrimitive[];
9
- min: number;
10
- max: number;
11
- buckets?: number;
12
- selectedRange: [number, number] | null;
13
- onChange: (range: [number, number] | null) => void;
14
- }
15
-
16
- interface HistogramBucket {
17
- start: number;
18
- end: number;
19
- count: number;
20
- maxCount: number;
21
- }
22
-
23
- export function RangeHistogramFilter({
24
- values,
25
- min,
26
- max,
27
- buckets = 20,
28
- selectedRange,
29
- onChange,
30
- }: RangeHistogramFilterProps) {
31
- const containerRef = useRef<HTMLDivElement>(null);
32
- const [isDragging, setIsDragging] = useState<'left' | 'right' | 'range' | null>(null);
33
- const [dragStart, setDragStart] = useState<{ x: number; range: [number, number] } | null>(null);
34
-
35
- const numericValues = useMemo(() => {
36
- return values
37
- .map((v) => {
38
- if (typeof v === 'number') return v;
39
- if (v instanceof Date) return v.getTime();
40
- const parsed = Number(v);
41
- return Number.isNaN(parsed) ? null : parsed;
42
- })
43
- .filter((v): v is number => v !== null);
44
- }, [values]);
45
-
46
- const histogram = useMemo((): HistogramBucket[] => {
47
- const range = max - min;
48
- if (range <= 0 || numericValues.length === 0) {
49
- return [];
50
- }
51
-
52
- const bucketSize = range / buckets;
53
- const bucketCounts: number[] = Array(buckets).fill(0);
54
-
55
- numericValues.forEach((value) => {
56
- const bucketIndex = Math.min(
57
- Math.floor((value - min) / bucketSize),
58
- buckets - 1
59
- );
60
- if (bucketIndex >= 0 && bucketIndex < buckets) {
61
- bucketCounts[bucketIndex]++;
62
- }
63
- });
64
-
65
- const maxCount = Math.max(...bucketCounts, 1);
66
-
67
- return bucketCounts.map((count, i) => ({
68
- start: min + i * bucketSize,
69
- end: min + (i + 1) * bucketSize,
70
- count,
71
- maxCount,
72
- }));
73
- }, [numericValues, min, max, buckets]);
74
-
75
- const currentRange = selectedRange ?? [min, max];
76
-
77
- const getPositionFromValue = useCallback(
78
- (value: number) => {
79
- const range = max - min;
80
- if (range <= 0) return 0;
81
- return ((value - min) / range) * 100;
82
- },
83
- [min, max]
84
- );
85
-
86
- const handleMouseDown = (
87
- e: React.MouseEvent,
88
- handle: 'left' | 'right' | 'range'
89
- ) => {
90
- (e as any).preventDefault?.();
91
- setIsDragging(handle);
92
- setDragStart({ x: e.clientX, range: [...currentRange] as [number, number] });
93
- };
94
-
95
- useEffect(() => {
96
- if (!isDragging || !dragStart || !containerRef.current) return;
97
-
98
- const container = containerRef.current;
99
- const rect = container.getBoundingClientRect();
100
- const range = max - min;
101
-
102
- const handleMouseMove = (e: MouseEvent) => {
103
- const deltaX = e.clientX - dragStart.x;
104
- const deltaPercent = (deltaX / rect.width) * 100;
105
- const deltaValue = (deltaPercent / 100) * range;
106
-
107
- let newRange: [number, number] = [...dragStart.range];
108
-
109
- if (isDragging === 'left') {
110
- newRange[0] = Math.max(min, Math.min(dragStart.range[0] + deltaValue, newRange[1] - range * 0.01));
111
- } else if (isDragging === 'right') {
112
- newRange[1] = Math.min(max, Math.max(dragStart.range[1] + deltaValue, newRange[0] + range * 0.01));
113
- } else if (isDragging === 'range') {
114
- const rangeWidth = dragStart.range[1] - dragStart.range[0];
115
- let newStart = dragStart.range[0] + deltaValue;
116
- let newEnd = dragStart.range[1] + deltaValue;
117
-
118
- if (newStart < min) {
119
- newStart = min;
120
- newEnd = min + rangeWidth;
121
- }
122
- if (newEnd > max) {
123
- newEnd = max;
124
- newStart = max - rangeWidth;
125
- }
126
-
127
- newRange = [newStart, newEnd];
128
- }
129
-
130
- onChange(newRange);
131
- };
132
-
133
- const handleMouseUp = () => {
134
- setIsDragging(null);
135
- setDragStart(null);
136
- };
137
-
138
- document.addEventListener('mousemove', handleMouseMove);
139
- document.addEventListener('mouseup', handleMouseUp);
140
-
141
- return () => {
142
- document.removeEventListener('mousemove', handleMouseMove);
143
- document.removeEventListener('mouseup', handleMouseUp);
144
- };
145
- }, [isDragging, dragStart, min, max, onChange]);
146
-
147
- const handleBarClick = (bucket: HistogramBucket) => {
148
- onChange([bucket.start, bucket.end]);
149
- };
150
-
151
- const handleClear = () => {
152
- onChange(null);
153
- };
154
-
155
- const leftPos = getPositionFromValue(currentRange[0]);
156
- const rightPos = getPositionFromValue(currentRange[1]);
157
-
158
- const formatValue = (value: number) => {
159
- if (Number.isInteger(value)) return value.toString();
160
- return value.toFixed(1);
161
- };
162
-
163
- return (
164
- <div className="pv-range-histogram" ref={containerRef}>
165
- <div className="pv-histogram-bars">
166
- {histogram.map((bucket, i) => {
167
- const heightPercent = (bucket.count / bucket.maxCount) * 100;
168
- const isInRange =
169
- bucket.start >= currentRange[0] && bucket.end <= currentRange[1];
170
- const isPartiallyInRange =
171
- bucket.end > currentRange[0] && bucket.start < currentRange[1];
172
-
173
- return (
174
- <button
175
- key={i}
176
- className={`pv-histogram-bar ${isInRange ? 'in-range' : ''} ${isPartiallyInRange && !isInRange ? 'partial' : ''}`}
177
- style={{ height: `${heightPercent}%` }}
178
- onClick={() => handleBarClick(bucket)}
179
- title={`${formatValue(bucket.start)} - ${formatValue(bucket.end)}: ${bucket.count} items`}
180
- type="button"
181
- />
182
- );
183
- })}
184
- </div>
185
-
186
- <div className="pv-range-slider">
187
- <div className="pv-range-track" />
188
- <div
189
- className="pv-range-selection"
190
- style={{
191
- left: `${leftPos}%`,
192
- width: `${rightPos - leftPos}%`,
193
- }}
194
- onMouseDown={(e) => handleMouseDown(e, 'range')}
195
- />
196
- <div
197
- className="pv-range-handle pv-range-handle-left"
198
- style={{ left: `${leftPos}%` }}
199
- onMouseDown={(e) => handleMouseDown(e, 'left')}
200
- />
201
- <div
202
- className="pv-range-handle pv-range-handle-right"
203
- style={{ left: `${rightPos}%` }}
204
- onMouseDown={(e) => handleMouseDown(e, 'right')}
205
- />
206
- </div>
207
-
208
- <div className="pv-range-labels">
209
- <span className="pv-range-value">{formatValue(currentRange[0])}</span>
210
- <span className="pv-range-value">{formatValue(currentRange[1])}</span>
211
- </div>
212
-
213
- {selectedRange && (
214
- <button type="button" className="pv-range-clear" onClick={handleClear}>
215
- Clear Range
216
- </button>
217
- )}
218
- </div>
219
- );
220
- }
@@ -1,21 +0,0 @@
1
- // Copyright (c) Cratis. All rights reserved.
2
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
-
4
- import './Spinner.css';
5
-
6
- export function Spinner() {
7
- return (
8
- <div className="pv-loading">
9
- <div className="pv-spinner">
10
- <div className="pv-spinner-ring" />
11
- <div className="pv-spinner-ring" />
12
- <div className="pv-spinner-ring" />
13
- <div className="pv-spinner-ring" />
14
- <div className="pv-spinner-ring" />
15
- <div className="pv-spinner-ring" />
16
- <div className="pv-spinner-ring" />
17
- <div className="pv-spinner-ring" />
18
- </div>
19
- </div>
20
- );
21
- }
@@ -1,130 +0,0 @@
1
- // Copyright (c) Cratis. All rights reserved.
2
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
-
4
- import type { PivotDimension } from '../types';
5
- import { ZOOM_MIN, ZOOM_MAX, ZOOM_STEP } from '../utils/utils';
6
-
7
- export type ViewMode = 'collection' | 'grouped';
8
-
9
- export interface ToolbarProps<TItem extends object> {
10
- hasFilters: boolean;
11
- filtersOpen: boolean;
12
- filteredCount: number;
13
- viewMode: ViewMode;
14
- zoomLevel: number;
15
- activeDimensionKey: string;
16
- dimensions: PivotDimension<TItem>[];
17
- activeFilterCount: number;
18
- onFiltersToggle: () => void;
19
- onViewModeChange: (mode: ViewMode) => void;
20
- onZoomIn: () => void;
21
- onZoomOut: () => void;
22
- onZoomSlider: (e: React.ChangeEvent<HTMLInputElement>) => void;
23
- onDimensionChange: (key: string) => void;
24
- filterButtonRef: React.RefObject<HTMLButtonElement | null>;
25
- }
26
-
27
- export function Toolbar<TItem extends object>({
28
- hasFilters,
29
- filtersOpen,
30
- filteredCount,
31
- viewMode,
32
- zoomLevel,
33
- activeDimensionKey,
34
- dimensions,
35
- activeFilterCount,
36
- onFiltersToggle,
37
- onViewModeChange,
38
- onZoomIn,
39
- onZoomOut,
40
- onZoomSlider,
41
- onDimensionChange,
42
- filterButtonRef,
43
- }: ToolbarProps<TItem>) {
44
- const labelText = 'Sort by';
45
-
46
- return (
47
- <header className="pv-toolbar">
48
- <div className="pv-toolbar-left">
49
- {hasFilters && (
50
- <button
51
- ref={filterButtonRef}
52
- type="button"
53
- className={`pv-filter-icon-button ${filtersOpen ? 'active' : ''}`}
54
- onClick={onFiltersToggle}
55
- title="Filters"
56
- >
57
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
58
- <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
59
- </svg>
60
- {activeFilterCount > 0 && (
61
- <span className="pv-filter-badge">{activeFilterCount}</span>
62
- )}
63
- </button>
64
- )}
65
- <h1>Pivot Viewer</h1>
66
- <span className="pv-count">{filteredCount} events</span>
67
- </div>
68
- <div className="pv-toolbar-right">
69
- <div className="pv-zoom-controls">
70
- <button
71
- type="button"
72
- onClick={onZoomOut}
73
- disabled={zoomLevel <= ZOOM_MIN}
74
- title="Zoom out"
75
- >
76
-
77
- </button>
78
- <input
79
- type="range"
80
- className="pv-zoom-slider"
81
- min={ZOOM_MIN}
82
- max={ZOOM_MAX}
83
- step={ZOOM_STEP}
84
- value={zoomLevel}
85
- onChange={onZoomSlider}
86
- title={`Zoom: ${Math.round(zoomLevel * 100)}%`}
87
- />
88
- <span className="pv-zoom-level">{Math.round(zoomLevel * 100)}%</span>
89
- <button
90
- type="button"
91
- onClick={onZoomIn}
92
- disabled={zoomLevel >= ZOOM_MAX}
93
- title="Zoom in"
94
- >
95
- +
96
- </button>
97
- </div>
98
- <div className="pv-view-toggle">
99
- <button
100
- type="button"
101
- className={viewMode === 'collection' ? 'active' : ''}
102
- onClick={() => onViewModeChange('collection')}
103
- >
104
- Collection
105
- </button>
106
- <button
107
- type="button"
108
- className={viewMode === 'grouped' ? 'active' : ''}
109
- onClick={() => onViewModeChange('grouped')}
110
- >
111
- Buckets
112
- </button>
113
- </div>
114
- <label className="pv-dimension-select">
115
- <span>{labelText}</span>
116
- <select
117
- value={activeDimensionKey}
118
- onChange={(event) => onDimensionChange(event.target.value)}
119
- >
120
- {dimensions.map((dimension) => (
121
- <option key={dimension.key} value={dimension.key}>
122
- {dimension.label}
123
- </option>
124
- ))}
125
- </select>
126
- </label>
127
- </div>
128
- </header>
129
- );
130
- }
@@ -1,10 +0,0 @@
1
- // Copyright (c) Cratis. All rights reserved.
2
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
-
4
- import { Toolbar, type ToolbarProps } from './Toolbar';
5
-
6
- export type ToolbarContainerProps<TItem extends object> = ToolbarProps<TItem>;
7
-
8
- export function ToolbarContainer<TItem extends object>(props: ToolbarContainerProps<TItem>) {
9
- return <Toolbar {...props} />;
10
- }
@@ -1,12 +0,0 @@
1
- // Copyright (c) Cratis. All rights reserved.
2
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
-
4
- export { PivotCanvas } from './PivotCanvas';
5
- export { FilterPanel } from './FilterPanel';
6
- export { Toolbar } from './Toolbar';
7
- export { DetailPanel } from './DetailPanel';
8
- export { AxisLabels } from './AxisLabels';
9
- export { Spinner } from './Spinner';
10
- export { RangeHistogramFilter } from './RangeHistogramFilter';
11
-
12
- export type { ViewMode } from './Toolbar';