@spteck/fluentui-react-charts 1.0.7 → 1.0.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.
Files changed (105) hide show
  1. package/dist/charts/BarChart/BarChart.d.ts +2 -1
  2. package/dist/charts/ComboChart/ComboChart.d.ts +2 -1
  3. package/dist/charts/Doughnut/DoughnutChart.d.ts +2 -1
  4. package/dist/charts/PieChart/PieChart.d.ts +2 -1
  5. package/dist/charts/areaChart/AreaChart.d.ts +2 -1
  6. package/dist/charts/barHorizontalChart/BarHotizontalChart.d.ts +2 -1
  7. package/dist/charts/bubbleChart/BubbleChart.d.ts +2 -1
  8. package/dist/charts/floatBarChart/FloatBarChart.d.ts +2 -1
  9. package/dist/charts/index.d.ts +14 -0
  10. package/dist/charts/lineChart/LineChart.d.ts +2 -1
  11. package/dist/charts/polarChart/PolarChart.d.ts +2 -1
  12. package/dist/charts/radarChart/RadarChart.d.ts +2 -1
  13. package/dist/charts/scatterChart/ScatterChart.d.ts +2 -1
  14. package/dist/charts/stackedLineChart/StackedLineChart.d.ts +2 -1
  15. package/dist/charts/steamChart/SteamChart.d.ts +2 -1
  16. package/dist/components/index.d.ts +0 -14
  17. package/dist/fluentui-react-charts.cjs.development.js +1086 -1072
  18. package/dist/fluentui-react-charts.cjs.development.js.map +1 -1
  19. package/dist/fluentui-react-charts.cjs.production.min.js +1 -1
  20. package/dist/fluentui-react-charts.cjs.production.min.js.map +1 -1
  21. package/dist/fluentui-react-charts.esm.js +1074 -1074
  22. package/dist/fluentui-react-charts.esm.js.map +1 -1
  23. package/dist/index.d.ts +1 -0
  24. package/package.json +3 -3
  25. package/src/assets/sample1.png +0 -0
  26. package/src/assets/sample2.png +0 -0
  27. package/src/assets/sample3.png +0 -0
  28. package/src/charts/BarChart/BarChart.tsx +0 -227
  29. package/src/charts/BarChart/README.MD +0 -335
  30. package/src/charts/BarChart/index.ts +0 -1
  31. package/src/charts/ComboChart/ComboChart.tsx +0 -209
  32. package/src/charts/ComboChart/README.MD +0 -347
  33. package/src/charts/ComboChart/index.ts +0 -1
  34. package/src/charts/Doughnut/DoughnutChart.tsx +0 -152
  35. package/src/charts/Doughnut/README.MD +0 -296
  36. package/src/charts/Doughnut/index.ts +0 -1
  37. package/src/charts/PieChart/PieChart.tsx +0 -148
  38. package/src/charts/PieChart/README.MD +0 -315
  39. package/src/charts/PieChart/index.ts +0 -1
  40. package/src/charts/areaChart/AreaChart.tsx +0 -195
  41. package/src/charts/areaChart/README.MD +0 -236
  42. package/src/charts/areaChart/index.ts +0 -1
  43. package/src/charts/barHorizontalChart/BarHotizontalChart.tsx +0 -200
  44. package/src/charts/barHorizontalChart/README.MD +0 -278
  45. package/src/charts/barHorizontalChart/index.ts +0 -2
  46. package/src/charts/bubbleChart/BubbleChart.tsx +0 -184
  47. package/src/charts/bubbleChart/README.MD +0 -275
  48. package/src/charts/bubbleChart/index.ts +0 -1
  49. package/src/charts/floatBarChart/FloatBarChart.tsx +0 -178
  50. package/src/charts/floatBarChart/README.MD +0 -354
  51. package/src/charts/floatBarChart/index.ts +0 -1
  52. package/src/charts/lineChart/LineChart.tsx +0 -200
  53. package/src/charts/lineChart/README.MD +0 -354
  54. package/src/charts/lineChart/index.ts +0 -1
  55. package/src/charts/polarChart/PolarChart.tsx +0 -161
  56. package/src/charts/polarChart/README.MD +0 -336
  57. package/src/charts/polarChart/index.ts +0 -1
  58. package/src/charts/radarChart/README.MD +0 -388
  59. package/src/charts/radarChart/RadarChart.tsx +0 -173
  60. package/src/charts/radarChart/index.ts +0 -1
  61. package/src/charts/scatterChart/README.MD +0 -335
  62. package/src/charts/scatterChart/ScatterChart.tsx +0 -155
  63. package/src/charts/scatterChart/index.ts +0 -1
  64. package/src/charts/stackedLineChart/README.MD +0 -396
  65. package/src/charts/stackedLineChart/StackedLineChart.tsx +0 -188
  66. package/src/charts/stackedLineChart/index.ts +0 -1
  67. package/src/charts/steamChart/README.MD +0 -414
  68. package/src/charts/steamChart/SteamChart.tsx +0 -236
  69. package/src/charts/steamChart/index.ts +0 -1
  70. package/src/components/RenderLabel/RenderLabel.tsx +0 -39
  71. package/src/components/RenderLabel/index.ts +0 -2
  72. package/src/components/RenderLabel/useRenderLabelStylesStyles.ts +0 -25
  73. package/src/components/RenderLegend/RenderLegend.tsx +0 -40
  74. package/src/components/RenderTooltip/RenderTooltip.tsx +0 -111
  75. package/src/components/buttonMenu/ButtonMenu.tsx +0 -186
  76. package/src/components/buttonMenu/IButtonMenuOption.ts +0 -9
  77. package/src/components/buttonMenu/IButtonMenuProps.tsx +0 -40
  78. package/src/components/dashboard/DashBoard.tsx +0 -314
  79. package/src/components/dashboard/ExampleDashboardUsage.tsx +0 -114
  80. package/src/components/dashboard/IDashboardProps.tsx +0 -11
  81. package/src/components/dashboard/NoDashboards.tsx +0 -26
  82. package/src/components/dashboard/index.ts +0 -3
  83. package/src/components/dashboard/selectZoom/SelectZoom.tsx +0 -184
  84. package/src/components/dashboard/useDashboardStyles.ts +0 -76
  85. package/src/components/index.ts +0 -17
  86. package/src/components/legendContainer/LegendContainer.tsx +0 -118
  87. package/src/components/legendeButton/LegendButton.tsx +0 -57
  88. package/src/components/renderSliceLegend/RenderSliceLegend.tsx +0 -46
  89. package/src/components/renderValueLegend/RenderValueLegend.tsx +0 -43
  90. package/src/components/stack/IStackProps.tsx +0 -94
  91. package/src/components/stack/Stack.tsx +0 -103
  92. package/src/components/svgImages/BusinessReportIcon.tsx +0 -218
  93. package/src/components/themeProvider/ThemeProvider.tsx +0 -48
  94. package/src/constants/Constants.tsx +0 -23
  95. package/src/graphGlobalStyles/useGraphGlobalStyles.ts +0 -28
  96. package/src/hooks/index.ts +0 -1
  97. package/src/hooks/useChartFactory.tsx +0 -136
  98. package/src/hooks/useChartUtils.tsx +0 -187
  99. package/src/hooks/useIndexedDBCache.ts +0 -119
  100. package/src/hooks/useResponsiveLegend.ts +0 -35
  101. package/src/index.tsx +0 -5
  102. package/src/models/ChartDatum.ts +0 -4
  103. package/src/models/ICardChartContainer.tsx +0 -11
  104. package/src/models/IChart.ts +0 -50
  105. package/src/models/index.ts +0 -3
@@ -1,314 +0,0 @@
1
- import { Card, CardHeader, Text, Theme } from '@fluentui/react-components';
2
- import React, {
3
- useCallback,
4
- useEffect,
5
- useMemo,
6
- useRef,
7
- useState,
8
- } from 'react';
9
-
10
- import { ICardChartContainer } from '../../models/ICardChartContainer';
11
- import { IDashboardProps } from './IDashboardProps';
12
- import { NoDashboards } from './NoDashboards';
13
- import { SelectZoom } from './selectZoom/SelectZoom';
14
- import { useChartFactory } from '../../hooks/useChartFactory';
15
- import { useDashboardStyles } from './useDashboardStyles';
16
- import { useIndexedDBCache } from '../../hooks/useIndexedDBCache';
17
-
18
- const MINIMUM_DASHBOARD_WIDTH = 600;
19
- const MAX_ROWS = 4;
20
- const DASHBOARD_LAYOUT_CACHE_KEY = 'dashboard-layout-settings';
21
- const DASHBOARD_ORDER_CACHE_KEY = 'dashboard-card-order';
22
- const CACHE_EXPIRATION_DAYS = 30;
23
-
24
- export const Dashboard: React.FC<IDashboardProps> = ({
25
- cardCharts,
26
- theme,
27
- containerWidth,
28
- containerHeight = '100%',
29
- maxSpanRows = MAX_ROWS,
30
- }) => {
31
- const styles = useDashboardStyles();
32
- const { getChartComponent } = useChartFactory();
33
-
34
- // Cache with 30-day expiration for dashboard layout settings
35
- const { getData, setData } = useIndexedDBCache<Record<string, { spanCols: number; spanRows: number }>>(
36
- CACHE_EXPIRATION_DAYS * 24 * 60 * 60 * 1000
37
- );
38
-
39
- // Cache for card order
40
- const {
41
- getData: getOrderData,
42
- setData: setOrderData
43
- } = useIndexedDBCache<string[]>(
44
- CACHE_EXPIRATION_DAYS * 24 * 60 * 60 * 1000
45
- );
46
-
47
-
48
-
49
- const [CardChartContainer, setCardChartContainer] = useState<
50
- ICardChartContainer[]
51
- >([]);
52
-
53
- const [sizes, setSizes] = useState<
54
- Record<string, { spanCols: number; spanRows: number }>
55
- >({});
56
- const dragItem = useRef<number | null>(null);
57
- const dragOverItem = useRef<number | null>(null);
58
- const containerRef = useRef<HTMLDivElement>(null);
59
-
60
- React.useEffect(() => {
61
- // Load cached card order and sizes
62
- const initializeData = async () => {
63
- try {
64
- const [cachedSizes, cachedOrder] = await Promise.all([
65
- getData(DASHBOARD_LAYOUT_CACHE_KEY),
66
- getOrderData(DASHBOARD_ORDER_CACHE_KEY)
67
- ]);
68
-
69
- // Restore card order if available, otherwise use original order
70
- let orderedCards = cardCharts;
71
- if (cachedOrder && cachedOrder.length > 0) {
72
- // Reorder cards based on cached order, but handle cases where cards might have been added/removed
73
- const cardMap = new Map(cardCharts.map(card => [card.id, card]));
74
- const validCachedOrder = cachedOrder.filter(id => cardMap.has(id));
75
- const missingCards = cardCharts.filter(card => !cachedOrder.includes(card.id));
76
-
77
- orderedCards = [
78
- ...validCachedOrder.map(id => cardMap.get(id)!),
79
- ...missingCards
80
- ];
81
- }
82
-
83
- setCardChartContainer(orderedCards);
84
-
85
- const initialSizes: Record<
86
- string,
87
- { spanCols: number; spanRows: number }
88
- > = {};
89
-
90
- cardCharts.forEach(c => {
91
- const cachedSize = cachedSizes?.[c.id];
92
- initialSizes[c.id] = {
93
- spanCols: cachedSize?.spanCols ?? c.defaultSpan?.spanCols ?? 1,
94
- spanRows: cachedSize?.spanRows ?? c.defaultSpan?.spanRows ?? 1,
95
- };
96
- });
97
-
98
- setSizes(initialSizes);
99
- } catch (error) {
100
- // Fallback to default values if cache fails
101
- console.warn('Failed to load dashboard data from cache:', error);
102
- setCardChartContainer(cardCharts);
103
- const fallbackSizes: Record<
104
- string,
105
- { spanCols: number; spanRows: number }
106
- > = {};
107
- cardCharts.forEach(c => {
108
- fallbackSizes[c.id] = {
109
- spanCols: c.defaultSpan?.spanCols ?? 1,
110
- spanRows: c.defaultSpan?.spanRows ?? 1,
111
- };
112
- });
113
- setSizes(fallbackSizes);
114
- }
115
- };
116
-
117
- initializeData();
118
- }, [cardCharts]); // Only depend on cardCharts - getData/getOrderData are stable
119
-
120
- // Save sizes to cache whenever they change
121
- useEffect(() => {
122
- const saveSizesToCache = async () => {
123
- try {
124
- await setData(DASHBOARD_LAYOUT_CACHE_KEY, sizes);
125
- } catch (error) {
126
- console.warn('Failed to save dashboard layout to cache:', error);
127
- }
128
- };
129
-
130
- // Only save if sizes is not empty (avoid saving initial empty state)
131
- if (Object.keys(sizes).length > 0) {
132
- saveSizesToCache();
133
- }
134
- }, [sizes]); // Only depend on sizes - setData is stable
135
-
136
- // Save card order to cache whenever it changes
137
- useEffect(() => {
138
- const saveOrderToCache = async () => {
139
- try {
140
- const cardOrder = CardChartContainer.map(card => card.id);
141
- await setOrderData(DASHBOARD_ORDER_CACHE_KEY, cardOrder);
142
- } catch (error) {
143
- console.warn('Failed to save dashboard order to cache:', error);
144
- }
145
- };
146
-
147
- // Only save if CardChartContainer is not empty
148
- if (CardChartContainer.length > 0) {
149
- saveOrderToCache();
150
- }
151
- }, [CardChartContainer]); // Only depend on CardChartContainer - setOrderData is stable
152
-
153
- useEffect(() => {
154
- if (containerWidth <= MINIMUM_DASHBOARD_WIDTH) {
155
- setSizes(() => {
156
- const reset: Record<
157
- string,
158
- { spanCols: number; spanRows: number }
159
- > = {};
160
- CardChartContainer.forEach(c => {
161
- reset[c.id] = { spanCols: 1, spanRows: 1 };
162
- });
163
- return reset;
164
- });
165
- }
166
- }, [containerWidth, CardChartContainer]);
167
-
168
- const showZoom = useMemo(() => containerWidth > MINIMUM_DASHBOARD_WIDTH, [
169
- containerWidth,
170
- ]);
171
-
172
- const { minCardWidth, gridGap, containerPadding } = useMemo(
173
- () => ({
174
- minCardWidth: 350,
175
- gridGap: 16,
176
- containerPadding: 20,
177
- }),
178
- []
179
- );
180
-
181
- const innerWidth = containerWidth - containerPadding * 2;
182
- const columns = useMemo(
183
- () => Math.floor((innerWidth + gridGap) / (minCardWidth + gridGap)),
184
- [innerWidth, gridGap, minCardWidth]
185
- );
186
- const maxZoom = Math.max(columns, 1);
187
-
188
- const handleSort = useCallback(() => {
189
- if (
190
- dragItem.current !== null &&
191
- dragOverItem.current !== null &&
192
- dragItem.current !== dragOverItem.current
193
- ) {
194
- const copy = [...CardChartContainer];
195
- const [moved] = copy.splice(dragItem.current, 1);
196
- copy.splice(dragOverItem.current, 0, moved);
197
- setCardChartContainer(copy);
198
- }
199
- dragItem.current = null;
200
- dragOverItem.current = null;
201
- }, [CardChartContainer]);
202
-
203
- const handleZoomSelect = useCallback(
204
- (id: string, span: { spanCols: number; spanRows: number }) => {
205
- const newSizes = {
206
- spanCols: Math.min(Math.max(span.spanCols, 1), maxZoom),
207
- spanRows: Math.min(Math.max(span.spanRows, 1), maxSpanRows),
208
- };
209
-
210
- setSizes(prev => ({
211
- ...prev,
212
- [id]: newSizes,
213
- }));
214
- },
215
- [maxZoom, maxSpanRows]
216
- );
217
-
218
- const defaultColsAndRowSpanBasedOnNumberColumns = useMemo(() => {
219
- return CardChartContainer.reduce((acc, card) => {
220
- acc[card.id] = {
221
- spanCols: Math.min(card.defaultSpan?.spanCols ?? 1, columns),
222
- spanRows: card.defaultSpan?.spanRows ?? 1,
223
- };
224
- return acc;
225
- }, {} as Record<string, { spanCols: number; spanRows: number }>);
226
- }, [CardChartContainer, columns]);
227
-
228
- const renderCards = useMemo(() => {
229
- return CardChartContainer.map((cardContainer, idx) => (
230
- <div
231
- className={styles.cardWrapper}
232
- key={cardContainer.id}
233
- draggable
234
- onDragStart={() => (dragItem.current = idx)}
235
- onDragEnter={() => (dragOverItem.current = idx)}
236
- onDragOver={e => e.preventDefault()}
237
- onDragEnd={handleSort}
238
- style={{
239
- gridColumnEnd: sizes[cardContainer.id]?.spanCols
240
- ? `span ${sizes[cardContainer.id].spanCols}`
241
- : `span ${defaultColsAndRowSpanBasedOnNumberColumns[
242
- cardContainer.id
243
- ]?.spanCols || 1}`,
244
- gridRowEnd: sizes[cardContainer.id]?.spanRows
245
- ? `span ${sizes[cardContainer.id].spanRows}`
246
- : `span ${defaultColsAndRowSpanBasedOnNumberColumns[
247
- cardContainer.id
248
- ]?.spanRows || 1}`,
249
- }}
250
- >
251
- <Card className={styles.cardBody}>
252
- <CardHeader
253
- header={
254
- <Text weight="semibold" size={400}>
255
- {cardContainer.cardTitle}
256
- </Text>
257
- }
258
- action={
259
- showZoom ? (
260
- <SelectZoom
261
- values={
262
- sizes[cardContainer.id] ||
263
- defaultColsAndRowSpanBasedOnNumberColumns[
264
- cardContainer.id
265
- ] || { spanCols: 1, spanRows: 1 }
266
- }
267
- maxCols={maxZoom}
268
- maxRows={maxSpanRows}
269
- IsOpen={false}
270
- onChange={v => handleZoomSelect(cardContainer.id, v)}
271
- />
272
- ) : (
273
- undefined
274
- )
275
- }
276
- />
277
- <div
278
- className={styles.chartContainer}
279
- style={{ height: containerHeight }}
280
- >
281
- {theme &&
282
- theme.fontSizeBase100 &&
283
- getChartComponent(cardContainer.chart, theme as Theme)}
284
- </div>
285
- </Card>
286
- </div>
287
- ));
288
- }, [
289
- CardChartContainer,
290
- handleSort,
291
- sizes,
292
- styles.cardBody,
293
- styles.chartContainer,
294
- showZoom,
295
- maxZoom,
296
- getChartComponent,
297
- handleZoomSelect,
298
- theme,
299
- ]);
300
-
301
- if (CardChartContainer.length === 0) {
302
- return <NoDashboards />;
303
- }
304
-
305
- return (
306
- <>
307
- <div ref={containerRef} className={styles.dashboardContainer} style={{ height: containerHeight }}>
308
- {renderCards}
309
- </div>
310
- </>
311
- );
312
- };
313
-
314
- export default Dashboard;
@@ -1,114 +0,0 @@
1
- import { Text, Theme, webLightTheme } from '@fluentui/react-components';
2
-
3
- import DashBoard from './DashBoard';
4
- import { ICardChartContainer } from '../../models/ICardChartContainer';
5
- import React from 'react';
6
- import { Stack } from '../stack/Stack';
7
-
8
- // Example usage of the reusable DashBoard component
9
- const ExampleDashboardUsage: React.FC<{ theme: Theme }> = ({ theme }) => {
10
- // Sample data for the charts
11
- const sampleData = [
12
- { month: 'Jan', sales: 10000, expenses: 8000 },
13
- { month: 'Feb', sales: 15000, expenses: 9000 },
14
- { month: 'Mar', sales: 12000, expenses: 7500 },
15
- { month: 'Apr', sales: 18000, expenses: 10000 },
16
- { month: 'May', sales: 16000, expenses: 8500 },
17
- { month: 'Jun', sales: 22000, expenses: 11000 },
18
- ];
19
-
20
- // Define the chart containers with different chart types
21
- const cardChartContainers: ICardChartContainer[] = [
22
- {
23
- id: '1',
24
- cardTitle: 'Monthly Sales',
25
- chart: {
26
- id: '1',
27
- title: 'Sales Performance',
28
- type: 'line',
29
- data: [{
30
- label: 'Sales',
31
- data: sampleData.map(d => ({ name: d.month, value: d.sales, x: d.month, y: d.sales }))
32
- }],
33
- },
34
- defaultSpan: { spanCols: 2, spanRows: 1 },
35
- },
36
- {
37
- id: '2',
38
- cardTitle: 'Sales vs Expenses',
39
- chart: {
40
- id: '2',
41
- title: 'Financial Overview',
42
- type: 'bar',
43
- data: [
44
- {
45
- label: 'Sales',
46
- data: sampleData.map(d => ({ name: d.month, value: d.sales, x: d.month, y: d.sales }))
47
- },
48
- {
49
- label: 'Expenses',
50
- data: sampleData.map(d => ({ name: d.month, value: d.expenses, x: d.month, y: d.expenses }))
51
- }
52
- ],
53
- },
54
- defaultSpan: { spanCols: 2, spanRows: 2 },
55
- },
56
- {
57
- id: '3',
58
- cardTitle: 'Revenue Distribution',
59
- chart: {
60
- id: '3',
61
- title: 'Q2 Revenue',
62
- type: 'pie',
63
- data: [{
64
- label: 'Revenue',
65
- data: [
66
- { name: 'Product A', value: 35000 },
67
- { name: 'Product B', value: 25000 },
68
- { name: 'Product C', value: 18000 },
69
- { name: 'Services', value: 15000 },
70
- ]
71
- }],
72
- },
73
- defaultSpan: { spanCols: 1, spanRows: 1 },
74
- },
75
- {
76
- id: '4',
77
- cardTitle: 'Growth Trend',
78
- chart: {
79
- id: '4',
80
- title: 'Monthly Growth',
81
- type: 'area',
82
- data: [{
83
- label: 'Growth',
84
- data: sampleData.map(d => {
85
- const growthPercentage = ((d.sales - d.expenses) / d.expenses * 100);
86
- return {
87
- name: d.month,
88
- value: Number(growthPercentage.toFixed(1)),
89
- x: d.month,
90
- y: Number(growthPercentage.toFixed(1))
91
- };
92
- })
93
- }],
94
- },
95
- defaultSpan: { spanCols: 2, spanRows: 1 },
96
- },
97
- ];
98
-
99
- return (
100
- <Stack direction="vertical" style={{ width: '100%', height: '100vh' }}>
101
- <Stack padding="l">
102
- <Text as="h1" size={900} weight="bold">Sample Dashboard</Text>
103
- </Stack>
104
- <DashBoard
105
- cardCharts={cardChartContainers}
106
- theme={theme ?? webLightTheme}
107
- containerWidth={window.innerWidth}
108
- containerHeight={(window.innerHeight - 100).toString()} // Adjust height for header
109
- />
110
- </Stack>
111
- );
112
- };
113
-
114
- export default ExampleDashboardUsage;
@@ -1,11 +0,0 @@
1
- import { ICardChartContainer } from '../../models/ICardChartContainer';
2
- import { Theme } from '@fluentui/react-components';
3
-
4
- export interface IDashboardProps {
5
- cardCharts: ICardChartContainer[];
6
- theme: Theme;
7
- containerWidth: number;
8
- containerHeight?: string;
9
-
10
- maxSpanRows?: number;
11
- }
@@ -1,26 +0,0 @@
1
- import * as React from 'react';
2
-
3
- import BusinessReportIcon from '../svgImages/BusinessReportIcon';
4
- import Stack from '../stack/Stack';
5
- import { Text } from '@fluentui/react-components';
6
-
7
- export interface INoDashboardsProps {
8
- height?: string;
9
- }
10
-
11
- export const NoDashboards: React.FunctionComponent<INoDashboardsProps> = (props: React.PropsWithChildren<INoDashboardsProps>) => {
12
- const { height } = props;
13
-
14
- return (
15
- <>
16
- <Stack
17
- style={{ height: height || "100%" }}
18
- justifyContent="Center"
19
- alignItems="Center"
20
- >
21
- <BusinessReportIcon width={200} height={200} />
22
- <Text size={500} weight='semibold'>No Dashboards Available</Text>
23
- </Stack>
24
- </>
25
- );
26
- };
@@ -1,3 +0,0 @@
1
- export * from './DashBoard';
2
-
3
-
@@ -1,184 +0,0 @@
1
- import * as React from "react";
2
-
3
- import {
4
- Caption1,
5
- Menu,
6
- MenuButton,
7
- MenuPopover,
8
- MenuProps,
9
- MenuTrigger,
10
- Tooltip,
11
- tokens,
12
- } from "@fluentui/react-components";
13
- import {
14
- Settings20Filled,
15
- Settings20Regular,
16
- bundleIcon,
17
- } from "@fluentui/react-icons";
18
-
19
- import { RenderLabel } from "../../RenderLabel";
20
- import { css } from "@emotion/css";
21
-
22
- export interface ISelectZoomProps {
23
- IsOpen: boolean;
24
- onChange?: (value: { spanCols: number; spanRows: number }) => void;
25
- values: { spanCols: number; spanRows: number };
26
- maxCols: number;
27
- maxRows: number;
28
- }
29
-
30
- const useStyles = (): {
31
- gridContainer: string;
32
- cell: string;
33
- hoveredCell: string;
34
- selectedCell: string;
35
- menuPopover: string;
36
- bottomText: string;
37
- zoomContainer: string;
38
- } => ({
39
- gridContainer: css`
40
- display: grid;
41
- gap: 4px;
42
- padding: 8px;
43
- justify-content: center;
44
- `,
45
- cell: css`
46
- width: 30px;
47
- height: 30px;
48
- border: 1px solid ${tokens.colorNeutralStroke1};
49
- background-color: ${tokens.colorNeutralBackground1};
50
- cursor: pointer;
51
- transition: background-color 150ms ease, transform 150ms ease;
52
- will-change: background-color, transform;
53
- `,
54
- hoveredCell: css`
55
- background-color: ${tokens.colorNeutralBackground1Hover};
56
- transform: scale(1.05);
57
- `,
58
- selectedCell: css`
59
- background-color: ${tokens.colorNeutralBackground1Selected};
60
- `,
61
- menuPopover: css`
62
- min-width: fit-content;
63
- `,
64
- bottomText: css`
65
- padding-left: 8px;
66
- padding-right: 8px;
67
- `,
68
- zoomContainer: css({
69
- display: 'flex',
70
- flexDirection: 'row',
71
- alignItems: 'center',
72
- gap: '6px',
73
- width: '100%',
74
- boxSizing: 'border-box',
75
-
76
- padding: '8px 0px',
77
-
78
- }),
79
- });
80
-
81
- export const SelectZoom: React.FunctionComponent<ISelectZoomProps> = (
82
- props: React.PropsWithChildren<ISelectZoomProps>
83
- ) => {
84
- const { onChange, values: defaultValues, maxCols, maxRows } = props;
85
- const Settings = bundleIcon(Settings20Filled, Settings20Regular);
86
- const styles = useStyles();
87
-
88
- const [values, setValues] = React.useState(defaultValues);
89
- const [hovered, setHovered] = React.useState<{
90
- spanCols: number;
91
- spanRows: number;
92
- } | null>(null);
93
- const [open, setOpen] = React.useState(false);
94
-
95
- React.useEffect(() => {
96
- setValues(defaultValues);
97
- }, [defaultValues]);
98
-
99
- const onOpenChange: MenuProps["onOpenChange"] = (_, data) => {
100
- setOpen(data.open);
101
- setHovered(null);
102
- };
103
-
104
- const handleCellClick = (row: number, col: number):void => {
105
- const newValues = { spanCols: col + 1, spanRows: row + 1 };
106
- setValues(newValues);
107
- onChange?.(newValues);
108
- setOpen(false);
109
- };
110
-
111
- const handleCellHover = (row: number, col: number):void => {
112
- setHovered({ spanCols: col + 1, spanRows: row + 1 });
113
- };
114
-
115
- const handleMouseLeave = ():void => {
116
- setHovered(null);
117
- };
118
-
119
- const renderGridCells = (): React.ReactNode[] => {
120
- const cells: React.ReactNode[] = [];
121
- for (let row = 0; row < maxRows; row++) {
122
- for (let col = 0; col < maxCols; col++) {
123
- const isSelected = row < values.spanRows && col < values.spanCols;
124
- const isHovered =
125
- hovered && row < hovered.spanRows && col < hovered.spanCols;
126
- cells.push(
127
- <div
128
- key={`${row}-${col}`}
129
- className={`${styles.cell} ${isHovered ? styles.hoveredCell : ""} ${
130
- isSelected ? styles.selectedCell : ""
131
- }`}
132
- onMouseEnter={() => handleCellHover(row, col)}
133
- onClick={() => handleCellClick(row, col)}
134
- />
135
- );
136
- }
137
- }
138
- return cells;
139
- };
140
-
141
- // Compute popover width dynamically
142
- const popoverWidth = React.useMemo(() => (30 * maxCols) + (4 * (maxCols - 1)) + 32, [maxCols]); // 30px for each cell, 4px gap, and 32px padding (16 left and right)
143
-
144
- return (
145
- <Menu open={open} onOpenChange={onOpenChange}>
146
- <MenuTrigger disableButtonEnhancement>
147
- <Tooltip content="Configure" relationship="label">
148
- <MenuButton
149
- icon={<Settings />}
150
- size="small"
151
- appearance="transparent"
152
- />
153
- </Tooltip>
154
- </MenuTrigger>
155
-
156
- <MenuPopover
157
- style={{ width: `${popoverWidth}px`, minWidth: "120px", padding: 8 }}
158
- >
159
- <div
160
- className={styles.zoomContainer}
161
- >
162
- <RenderLabel
163
- label={`Span (${values.spanCols} × ${values.spanRows})`}
164
-
165
- />
166
- </div>
167
-
168
- {/* Grid */}
169
- <div
170
- className={styles.gridContainer}
171
- style={{
172
- gridTemplateColumns: `repeat(${maxCols}, 30px)`,
173
- gridTemplateRows: `repeat(${maxRows}, 30px)`,
174
- }}
175
- onMouseLeave={handleMouseLeave}
176
- >
177
- {renderGridCells()}
178
- </div>
179
-
180
- <Caption1 className={styles.bottomText}>Click to set span</Caption1>
181
- </MenuPopover>
182
- </Menu>
183
- );
184
- };