Skip to content

Commit 73efe14

Browse files
authored
version(patch): Merge pull request #90 from sathish151198/rfk-sync
feat(rfk): restrict field item drop in bucket
2 parents 43e95ee + 5487bbc commit 73efe14

6 files changed

Lines changed: 358 additions & 19 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/* Warning Tooltip Styles */
2+
.react-fields-keeper-warning-inline {
3+
position: absolute;
4+
top: -80px;
5+
left: 8px;
6+
right: 8px;
7+
background-color: white;
8+
border: 1px solid #ccc;
9+
border-radius: 6px;
10+
padding: 3px 10px 3px 3px;
11+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
12+
z-index: 1000;
13+
min-width: 150px;
14+
max-width: 100%;
15+
font-size: 14px;
16+
color: #856404;
17+
word-wrap: break-word;
18+
}
19+
20+
.react-fields-keeper-warning-tooltip {
21+
display: flex;
22+
align-items: center;
23+
gap: 8px;
24+
min-height: 20px;
25+
flex-wrap: wrap;
26+
}
27+
28+
.custom-component-warning-tooltip {
29+
display: flex;
30+
align-items: flex-start;
31+
gap: 8px;
32+
min-height: 20px;
33+
}
34+
35+
.tooltip-box {
36+
position: relative;
37+
border-radius: 4px;
38+
padding: 3px 10px 3px 3px;
39+
width: 180px;
40+
max-width: 300px;
41+
font-family: Segoe UI, sans-serif;
42+
font-size: 13px;
43+
color: #323130;
44+
line-height: 1.4;
45+
max-height: 80px;
46+
overflow: scroll;
47+
}
48+
49+
.tooltip-arrow {
50+
position: absolute;
51+
bottom: -8px;
52+
left: 85px;
53+
width: 0;
54+
height: 0;
55+
border-left: 8px solid transparent;
56+
border-right: 8px solid transparent;
57+
border-top: 8px solid #ccc;
58+
}
59+
60+
.tooltip-arrow::after {
61+
content: '';
62+
position: absolute;
63+
left: -7px;
64+
top: -9px;
65+
width: 0;
66+
height: 0;
67+
border-left: 7px solid transparent;
68+
border-right: 7px solid transparent;
69+
border-top: 7px solid #fff;
70+
}
71+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React, { useEffect } from 'react';
2+
import './WarningTooltip.css';
3+
4+
interface IWarningTooltipProps {
5+
/** Whether the warning tooltip is visible */
6+
isVisible: boolean;
7+
8+
/** The warning message - can be a string or JSX element */
9+
message: string | JSX.Element | null;
10+
11+
/** Callback function to close the warning tooltip */
12+
onClose: () => void;
13+
14+
/** Auto-close timeout in milliseconds (only for inline mode) */
15+
autoCloseTimeout: number;
16+
}
17+
18+
export const WarningTooltip: React.FC<IWarningTooltipProps> = ({
19+
isVisible,
20+
message,
21+
onClose,
22+
autoCloseTimeout = 3000,
23+
}) => {
24+
// Auto-close effect for inline mode
25+
useEffect(() => {
26+
if (isVisible && autoCloseTimeout > 0) {
27+
const timer = setTimeout(() => {
28+
onClose();
29+
}, autoCloseTimeout);
30+
31+
return () => clearTimeout(timer);
32+
}
33+
}, [isVisible, autoCloseTimeout, onClose]);
34+
35+
if (!isVisible || !message) {
36+
return null;
37+
}
38+
39+
return (
40+
<div className="react-fields-keeper-warning-inline" onClick={onClose}>
41+
{typeof message === 'string' ? (
42+
<div className="react-fields-keeper-warning-tooltip">
43+
<div className="tooltip-box">{message}</div>
44+
<div className="tooltip-arrow" />
45+
</div>
46+
) : (
47+
<div className="custom-component-warning-tooltip">
48+
{message}
49+
</div>
50+
)}
51+
</div>
52+
);
53+
};
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import { useState } from 'react';
2+
import {
3+
FieldsKeeperProvider,
4+
FieldsKeeperBucket,
5+
FieldsKeeperRootBucket,
6+
IFieldsKeeperItem,
7+
IFieldsKeeperBucket,
8+
IRootBucketFieldItemLabelChangeProps,
9+
} from '..';
10+
11+
export default function Example38() {
12+
// compute
13+
const allItems: IFieldsKeeperItem[] = [
14+
{
15+
id: 'a',
16+
label: 'a',
17+
folders: ['folder_1'],
18+
},
19+
{
20+
id: 'b',
21+
label: 'b',
22+
folders: ['folder_1'],
23+
},
24+
{
25+
id: 'c',
26+
label: 'c',
27+
folders: ['folder_2'],
28+
},
29+
{
30+
id: 'd',
31+
label: 'd',
32+
folders: ['folder_2'],
33+
},
34+
{
35+
id: 'date.quarter',
36+
label: 'Quarter',
37+
folders: ['folder_2'],
38+
group: 'date',
39+
groupLabel: 'Date',
40+
groupOrder: 1,
41+
},
42+
{
43+
id: 'date.year',
44+
label: 'Year',
45+
folders: ['folder_2'],
46+
group: 'date',
47+
groupLabel: 'Date',
48+
groupOrder: 0,
49+
},
50+
{
51+
id: 'date.month',
52+
label: 'Month',
53+
folders: ['folder_2'],
54+
group: 'date',
55+
groupLabel: 'Date',
56+
groupOrder: 2,
57+
},
58+
{
59+
id: 'date.day',
60+
label: 'Day',
61+
folders: ['folder_2'],
62+
group: 'date',
63+
groupLabel: 'Date',
64+
groupOrder: 3,
65+
},
66+
];
67+
68+
const [buckets, setBuckets] = useState<IFieldsKeeperBucket[]>([
69+
{ id: 'bucket1', items: [allItems[0]] },
70+
{
71+
id: 'bucket2',
72+
items: [allItems[1], allItems[2]],
73+
},
74+
{ id: 'bucket3', items: [] },
75+
]);
76+
77+
78+
const [rootItems, setRootItems] = useState<IFieldsKeeperItem[]>(allItems);
79+
80+
const updateRootFieldLabel = (
81+
fieldItemLabelClickProps: IRootBucketFieldItemLabelChangeProps,
82+
) => {
83+
const {
84+
fieldItem,
85+
newValue,
86+
} = fieldItemLabelClickProps;
87+
88+
console.log(`Updating root field label: ${fieldItem.label} -> ${newValue}`);
89+
90+
// Update root items
91+
const updatedRootItems = rootItems.map((item) => {
92+
if (item.group === fieldItem.id) {
93+
return { ...item, groupLabel: newValue };
94+
}
95+
if (item.id === fieldItem.id) {
96+
return { ...item, label: newValue };
97+
}
98+
return item;
99+
});
100+
101+
setRootItems(updatedRootItems);
102+
103+
// Also update any items that might be in buckets to keep them in sync
104+
const updatedBuckets = buckets.map((bucket) => ({
105+
...bucket,
106+
items: bucket.items.map((item) => {
107+
if (item.group === fieldItem.id) {
108+
return { ...item, groupLabel: newValue };
109+
}
110+
if (item.id === fieldItem.id) {
111+
return { ...item, label: newValue };
112+
}
113+
return item;
114+
}),
115+
}));
116+
117+
setBuckets(updatedBuckets);
118+
};
119+
120+
// paint
121+
return (
122+
<div className="example-container">
123+
<div className="example-container-title">39. Restrict Bucket Drop</div>
124+
<FieldsKeeperProvider
125+
allItems={rootItems}
126+
buckets={buckets}
127+
onUpdate={(state) => {
128+
console.log(state);
129+
}}
130+
accentColor={'#117865'}
131+
foldersMeta={{
132+
folder_1: {
133+
id: 'folder_1',
134+
label: 'Folder 1',
135+
prefixNodeIcon: 'folder-icon',
136+
},
137+
folder_2: {
138+
id: 'folder_2',
139+
label: 'Folder 2',
140+
prefixNodeIcon: 'folder-icon',
141+
},
142+
}}
143+
>
144+
<div className="keeper-container">
145+
<div className="buckets-container">
146+
<FieldsKeeperBucket
147+
id="bucket1"
148+
label="Bucket 1"
149+
allowRemoveFields
150+
/>
151+
<FieldsKeeperBucket
152+
id="bucket2"
153+
label="Bucket 2"
154+
allowRemoveFields
155+
/>
156+
<FieldsKeeperBucket
157+
id="bucket3"
158+
label="Bucket 3"
159+
allowRemoveFields
160+
onBucketDropBlockHandler={() => {
161+
return {isShouldBlockAssignment: true, warningMessage: "This field can't be used here because the data model has discourage implicit measures property enabled, and a measure is required here. This may be due to the use of calculation groups or the property is enabled directly on the model"}
162+
}}
163+
/>
164+
</div>
165+
<div className="root-bucket-container">
166+
<FieldsKeeperRootBucket
167+
label="Root Bucket"
168+
collapseFoldersOnMount={false}
169+
prefixNode={{ allow: true, reserveSpace: true }}
170+
onFieldItemLabelChange={(
171+
fieldItemLabelClickProps: IRootBucketFieldItemLabelChangeProps,
172+
) => {
173+
updateRootFieldLabel(fieldItemLabelClickProps);
174+
}}
175+
/>
176+
</div>
177+
</div>
178+
</FieldsKeeperProvider>
179+
</div>
180+
);
181+
}

src/Examples/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import Example35_Cross_Highlight_Across_Buckets from './Example35_Cross_Highligh
3636
import Example36_bucketLabelSuffixRenderer from './Example36_bucketLabelSuffixRenderer';
3737
import Example37_bucket_move_suffix from './Example37_bucket_move_suffix';
3838
import Example38_root_bucket_label_editing from './Example38_root_bucket_label_editing';
39+
import Example39_restrict_bucket_drop from './Example39_restrict_bucket_drop';
3940

4041
export const examples = {
4142
Example1_sample_use_case,
@@ -75,5 +76,6 @@ export const examples = {
7576
Example35_Cross_Highlight_Across_Buckets,
7677
Example36_bucketLabelSuffixRenderer,
7778
Example37_bucket_move_suffix,
78-
Example38_root_bucket_label_editing
79+
Example38_root_bucket_label_editing,
80+
Example39_restrict_bucket_drop
7981
};

src/FieldsKeeper/FieldsKeeper.types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,13 @@ export interface IFieldsKeeperBucketProps {
645645
* @default true
646646
*/
647647
allowDragging?: boolean;
648+
649+
onBucketDropBlockHandler?: () => IBucketDropBlock;
650+
}
651+
652+
export interface IBucketDropBlock {
653+
isShouldBlockAssignment: boolean;
654+
warningMessage: string | JSX.Element;
648655
}
649656

650657
export interface IFieldItemLabelChangeProps {

0 commit comments

Comments
 (0)