@visns-studio/visns-components 5.8.20 → 5.9.0

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/README.md CHANGED
@@ -1375,6 +1375,155 @@ We welcome contributions! Please follow these steps:
1375
1375
 
1376
1376
  [MIT License](LICENSE)
1377
1377
 
1378
+ ## Modal Actions in Components
1379
+
1380
+ ### Using Modal Actions in GenericQuote Component
1381
+
1382
+ Modal actions allow you to open a form modal when a user clicks an action button. This is useful for collecting additional information from users or submitting data related to the quote.
1383
+
1384
+ #### Implementation Methods
1385
+
1386
+ There are two ways to implement this functionality:
1387
+
1388
+ **1. Using the "addModal" type:**
1389
+
1390
+ ```javascript
1391
+ {
1392
+ "id": "anyCustomId1",
1393
+ "label": "Submit Form",
1394
+ "type": "addModal",
1395
+ "setting": {
1396
+ // Form settings go here (see structure below)
1397
+ }
1398
+ }
1399
+ ```
1400
+
1401
+ **2. Using the "modal" type (alternative approach):**
1402
+
1403
+ ```javascript
1404
+ {
1405
+ "id": "anyCustomId2",
1406
+ "label": "Submit Form",
1407
+ "type": "modal",
1408
+ "setting": {
1409
+ // Form settings go here (see structure below)
1410
+ }
1411
+ }
1412
+ ```
1413
+
1414
+ Both approaches are treated identically in the code.
1415
+
1416
+ #### Form Settings Structure
1417
+
1418
+ The `setting` property MUST follow this specific structure:
1419
+
1420
+ ```javascript
1421
+ "setting": {
1422
+ // IMPORTANT: The 'create' property is required for the form to work properly
1423
+ "create": {
1424
+ "title": "Submit New Form",
1425
+ "submitUrl": "/api/forms/submit",
1426
+ "method": "POST"
1427
+ },
1428
+ // The 'fields' array is required to define the form fields
1429
+ "fields": [
1430
+ {
1431
+ "id": "name",
1432
+ "label": "Name",
1433
+ "type": "text",
1434
+ "required": "yes",
1435
+ "size": "half"
1436
+ },
1437
+ {
1438
+ "id": "email",
1439
+ "label": "Email",
1440
+ "type": "email",
1441
+ "required": "yes",
1442
+ "size": "half"
1443
+ },
1444
+ {
1445
+ "id": "message",
1446
+ "label": "Message",
1447
+ "type": "textarea",
1448
+ "required": "yes",
1449
+ "size": "full"
1450
+ }
1451
+ ]
1452
+ }
1453
+ ```
1454
+
1455
+ #### Required Form Settings Properties
1456
+
1457
+ - `create`: Object containing settings for the create form (REQUIRED)
1458
+
1459
+ - `title`: The title displayed at the top of the modal
1460
+ - `submitUrl`: The URL to submit the form data to
1461
+ - `method`: The HTTP method to use (POST, PUT, etc.)
1462
+
1463
+ - `fields`: Array of field objects that define the form fields (REQUIRED)
1464
+ - `id`: Unique identifier for the field
1465
+ - `label`: Display label for the field
1466
+ - `type`: Field type (text, email, textarea, select, etc.)
1467
+ - `required`: Whether the field is required ("yes" or "no")
1468
+ - `size`: Field size in the form layout ("full", "half", "quarter")
1469
+ - Additional properties depending on the field type
1470
+
1471
+ ## Troubleshooting
1472
+
1473
+ ### DataGrid Checkbox Issues
1474
+
1475
+ #### Checkbox Cell Click Bug Fix
1476
+
1477
+ **Problem:** Clicking anywhere within a checkbox cell (but outside the actual checkbox input) incorrectly cleared all other selected checkboxes, leaving only the clicked row selected.
1478
+
1479
+ **Solution:** Enhanced click detection to distinguish between checkbox input clicks and cell area clicks. The fix ensures that both checkbox input clicks and cell area clicks behave identically - toggling only the specific row while maintaining all other selections.
1480
+
1481
+ **Files Modified:**
1482
+
1483
+ - `src/components/crm/DataGrid.jsx` - Enhanced click handling logic
1484
+ - `src/components/crm/styles/global-datagrid.css` - Improved cell styling
1485
+
1486
+ #### Checkbox Improvements
1487
+
1488
+ The DataGrid checkbox functionality has been enhanced with:
1489
+
1490
+ - Larger touch targets for better mobile usability
1491
+ - Consistent behavior between input and cell area clicks
1492
+ - Proper selection state management
1493
+ - Improved visual feedback
1494
+
1495
+ ### Modal Action Troubleshooting
1496
+
1497
+ If the modal doesn't open when clicking the action button, check the following:
1498
+
1499
+ 1. Make sure the action object has either:
1500
+
1501
+ - `id: "addModal"` or
1502
+ - `type: "modal"`
1503
+
1504
+ 2. Check the browser console for any errors or debug messages
1505
+
1506
+ 3. **IMPORTANT**: Verify that the `setting` property has the REQUIRED structure:
1507
+
1508
+ - It MUST have a `create` object containing at least `title`, `submitUrl`, and `method`
1509
+ - It MUST have a `fields` array with properly defined field objects
1510
+
1511
+ 4. If using the default form settings (without a custom `setting` property), make sure the component has valid form settings in its props.
1512
+
1513
+ ## Development Notes
1514
+
1515
+ ### DataGrid Layout Optimization
1516
+
1517
+ The DataGrid component has been optimized for vertical space by conditionally relocating the auto refresh control when it's the sole UI control present. This provides better space utilization and cleaner interfaces.
1518
+
1519
+ ### Modal Alignment Implementation
1520
+
1521
+ Modal components support vertical alignment configuration with the `verticalAlign` property. Setting `verticalAlign: 'top'` positions modals at 5vh from the top of the viewport for better user experience.
1522
+
1523
+ ### Grouping Investigation Results
1524
+
1525
+ The ReactDataGrid's `defaultGroupBy` prop enables internal grouping functionality that creates collapsible group headers within the table structure. It does NOT create any external UI elements that need relocation, as all grouping controls are internal to the table component.
1526
+
1378
1527
  ## Support
1379
1528
 
1380
1529
  For support, please contact the VISNS Studio team or open an issue on GitHub.
package/package.json CHANGED
@@ -86,7 +86,7 @@
86
86
  "react-dom": "^17.0.0 || ^18.0.0"
87
87
  },
88
88
  "name": "@visns-studio/visns-components",
89
- "version": "5.8.20",
89
+ "version": "5.9.0",
90
90
  "description": "Various packages to assist in the development of our Custom Applications.",
91
91
  "main": "src/index.js",
92
92
  "files": [
@@ -43,6 +43,7 @@ import { toast } from 'react-toastify';
43
43
  import imageCompression from 'browser-image-compression';
44
44
  import Vapor from 'laravel-vapor';
45
45
  import Lightbox from 'yet-another-react-lightbox';
46
+ import { Tooltip } from 'react-tooltip';
46
47
  import fetchUtil from '../../utils/fetchUtil';
47
48
  import { confirmDialog } from '../utils/ConfirmDialog';
48
49
  import {
@@ -146,6 +147,123 @@ const loadData = async (
146
147
  }
147
148
  };
148
149
 
150
+ // Utility function to check if text content is truncated
151
+ const isTextTruncated = (element) => {
152
+ if (!element) return false;
153
+ return (
154
+ element.scrollWidth > element.clientWidth ||
155
+ element.scrollHeight > element.clientHeight
156
+ );
157
+ };
158
+
159
+ // Utility function to format cell value for tooltip display
160
+ const formatCellValueForTooltip = (value, columnType) => {
161
+ if (value === null || value === undefined || value === '') {
162
+ return '';
163
+ }
164
+
165
+ switch (columnType) {
166
+ case 'date':
167
+ case 'datetime':
168
+ if (
169
+ moment(value).isValid() &&
170
+ moment(value).format('YYYY-MM-DD') !== '1970-01-01'
171
+ ) {
172
+ return moment(value).format('DD-MM-YYYY HH:mm');
173
+ }
174
+ return String(value);
175
+ case 'currency':
176
+ return numeral(value).format('$0,0.00');
177
+ case 'number':
178
+ return numeral(value).format('0,0');
179
+ case 'boolean':
180
+ return value ? 'Yes' : 'No';
181
+ case 'richtext':
182
+ // Strip HTML tags for tooltip
183
+ const tempDiv = document.createElement('div');
184
+ tempDiv.innerHTML = value;
185
+ return tempDiv.textContent || tempDiv.innerText || '';
186
+ default:
187
+ return String(value);
188
+ }
189
+ };
190
+
191
+ // Component to wrap cell content with tooltip functionality
192
+ const CellWithTooltip = ({
193
+ children,
194
+ value,
195
+ columnType = 'text',
196
+ className = '',
197
+ }) => {
198
+ const cellRef = useRef(null);
199
+ const [showTooltip, setShowTooltip] = useState(false);
200
+ const [tooltipId] = useState(
201
+ () => `cell-tooltip-${Math.random().toString(36).substr(2, 9)}`
202
+ );
203
+
204
+ useEffect(() => {
205
+ const checkTruncation = () => {
206
+ if (cellRef.current) {
207
+ const isTruncated = isTextTruncated(cellRef.current);
208
+ setShowTooltip(isTruncated);
209
+ }
210
+ };
211
+
212
+ // Check truncation on mount and when content changes
213
+ checkTruncation();
214
+
215
+ // Also check on window resize
216
+ const handleResize = () => checkTruncation();
217
+ window.addEventListener('resize', handleResize);
218
+
219
+ return () => {
220
+ window.removeEventListener('resize', handleResize);
221
+ };
222
+ }, [children, value]);
223
+
224
+ const tooltipContent = formatCellValueForTooltip(value, columnType);
225
+
226
+ // Only show tooltip if content is truncated and we have meaningful content
227
+ const shouldShowTooltip =
228
+ showTooltip && tooltipContent && tooltipContent.trim() !== '';
229
+
230
+ return (
231
+ <div
232
+ ref={cellRef}
233
+ className={className}
234
+ style={{
235
+ overflow: 'hidden',
236
+ textOverflow: 'ellipsis',
237
+ whiteSpace: 'nowrap',
238
+ width: '100%',
239
+ }}
240
+ {...(shouldShowTooltip && {
241
+ 'data-tooltip-id': tooltipId,
242
+ 'data-tooltip-content': tooltipContent,
243
+ })}
244
+ >
245
+ {children}
246
+ {shouldShowTooltip && (
247
+ <Tooltip
248
+ id={tooltipId}
249
+ style={{
250
+ backgroundColor: 'rgba(0, 0, 0, 0.9)',
251
+ color: 'white',
252
+ borderRadius: '6px',
253
+ padding: '8px 12px',
254
+ fontSize: '13px',
255
+ maxWidth: '300px',
256
+ wordWrap: 'break-word',
257
+ zIndex: 9999,
258
+ }}
259
+ place="top"
260
+ offset={5}
261
+ />
262
+ )}
263
+ </div>
264
+ );
265
+ };
266
+
149
267
  const DataGrid = forwardRef(
150
268
  (
151
269
  {
@@ -378,6 +496,18 @@ const DataGrid = forwardRef(
378
496
  }
379
497
 
380
498
  sqlResult.then((res) => {
499
+ // Sort data by grouping field if grouping is enabled
500
+ if (ajaxSetting?.groupBy?.length > 0) {
501
+ const groupField = ajaxSetting.groupBy[0]; // groupBy is array of strings
502
+ res.data.sort((a, b) => {
503
+ const aValue = a[groupField] || '';
504
+ const bValue = b[groupField] || '';
505
+ return aValue
506
+ .toString()
507
+ .localeCompare(bValue.toString());
508
+ });
509
+ }
510
+
381
511
  if (setTotal) {
382
512
  setTotal(res.count);
383
513
  }
@@ -1538,10 +1668,11 @@ const DataGrid = forwardRef(
1538
1668
  'var(--light-bg-color, #f5f5f5)',
1539
1669
  borderRadius: '4px',
1540
1670
  padding: '4px',
1541
- width: '22px',
1542
- height: '22px',
1671
+ width: '28px',
1672
+ height: '28px',
1543
1673
  boxShadow: '0 1px 2px rgba(0,0,0,0.05)',
1544
1674
  margin: '0 1px',
1675
+ cursor: 'pointer',
1545
1676
  }}
1546
1677
  >
1547
1678
  <img
@@ -1550,8 +1681,8 @@ const DataGrid = forwardRef(
1550
1681
  data-tooltip-id="system-tooltip"
1551
1682
  data-tooltip-content={tooltipContent}
1552
1683
  style={{
1553
- maxHeight: '16px',
1554
- maxWidth: '16px',
1684
+ maxHeight: '20px',
1685
+ maxWidth: '20px',
1555
1686
  objectFit: 'contain',
1556
1687
  }}
1557
1688
  />
@@ -1586,10 +1717,11 @@ const DataGrid = forwardRef(
1586
1717
  'var(--light-bg-color, #f5f5f5)',
1587
1718
  borderRadius: '4px',
1588
1719
  padding: '4px',
1589
- width: '22px',
1590
- height: '22px',
1720
+ width: '28px',
1721
+ height: '28px',
1591
1722
  boxShadow: '0 1px 2px rgba(0,0,0,0.05)',
1592
1723
  margin: '0 1px',
1724
+ cursor: 'pointer',
1593
1725
  }}
1594
1726
  >
1595
1727
  <IconComponent
@@ -2133,7 +2265,14 @@ const DataGrid = forwardRef(
2133
2265
 
2134
2266
  const value = `${addressValue} ${stateValue}`;
2135
2267
 
2136
- return <span>{value}</span>;
2268
+ return (
2269
+ <CellWithTooltip
2270
+ value={value}
2271
+ columnType="address"
2272
+ >
2273
+ <span>{value}</span>
2274
+ </CellWithTooltip>
2275
+ );
2137
2276
  },
2138
2277
  };
2139
2278
  case 'age':
@@ -2190,11 +2329,17 @@ const DataGrid = forwardRef(
2190
2329
  name: `${column.type}.${column.id}`,
2191
2330
  sortable: false,
2192
2331
  render: ({ data }) => {
2193
- if (data[column.id]) {
2194
- return 'Yes';
2195
- } else {
2196
- return 'No';
2197
- }
2332
+ const value = data[column.id]
2333
+ ? 'Yes'
2334
+ : 'No';
2335
+ return (
2336
+ <CellWithTooltip
2337
+ value={data[column.id]}
2338
+ columnType="boolean"
2339
+ >
2340
+ <span>{value}</span>
2341
+ </CellWithTooltip>
2342
+ );
2198
2343
  },
2199
2344
  };
2200
2345
  case 'coding':
@@ -2278,7 +2423,14 @@ const DataGrid = forwardRef(
2278
2423
  data[column.id]
2279
2424
  ).format('$0,0.00');
2280
2425
 
2281
- return <span>{data}</span>;
2426
+ return (
2427
+ <CellWithTooltip
2428
+ value={data[column.id]}
2429
+ columnType="currency"
2430
+ >
2431
+ <span>{data}</span>
2432
+ </CellWithTooltip>
2433
+ );
2282
2434
  } else {
2283
2435
  return null;
2284
2436
  }
@@ -2328,9 +2480,14 @@ const DataGrid = forwardRef(
2328
2480
  }
2329
2481
 
2330
2482
  return (
2331
- <span style={columnStyle}>
2332
- {value}
2333
- </span>
2483
+ <CellWithTooltip
2484
+ value={data[column.id]}
2485
+ columnType="date"
2486
+ >
2487
+ <span style={columnStyle}>
2488
+ {value}
2489
+ </span>
2490
+ </CellWithTooltip>
2334
2491
  );
2335
2492
  } else {
2336
2493
  return null;
@@ -2451,7 +2608,13 @@ const DataGrid = forwardRef(
2451
2608
  }}
2452
2609
  >
2453
2610
  <option value="">
2454
- Select an Option
2611
+ Select{' '}
2612
+ {/^[aeiouAEIOU]/.test(
2613
+ column.label
2614
+ )
2615
+ ? 'an'
2616
+ : 'a'}{' '}
2617
+ {column.label}
2455
2618
  </option>
2456
2619
  {dropdownData[column.id] &&
2457
2620
  dropdownData[column.id].length >
@@ -2505,7 +2668,7 @@ const DataGrid = forwardRef(
2505
2668
  data-tooltip-id={`system-tooltip`}
2506
2669
  data-tooltip-content={`Download File ${file.file_name}`}
2507
2670
  strokeWidth={2}
2508
- size={18}
2671
+ size={14}
2509
2672
  className={
2510
2673
  styles.tdaction
2511
2674
  }
@@ -2537,7 +2700,7 @@ const DataGrid = forwardRef(
2537
2700
  .file_name
2538
2701
  }`}
2539
2702
  strokeWidth={2}
2540
- size={18}
2703
+ size={14}
2541
2704
  className={
2542
2705
  styles.tdaction
2543
2706
  }
@@ -2622,7 +2785,14 @@ const DataGrid = forwardRef(
2622
2785
  column.jsonData
2623
2786
  ];
2624
2787
 
2625
- return <span>{data}</span>;
2788
+ return (
2789
+ <CellWithTooltip
2790
+ value={data}
2791
+ columnType="json"
2792
+ >
2793
+ <span>{data}</span>
2794
+ </CellWithTooltip>
2795
+ );
2626
2796
  } else {
2627
2797
  return null;
2628
2798
  }
@@ -2668,7 +2838,14 @@ const DataGrid = forwardRef(
2668
2838
  ];
2669
2839
  }
2670
2840
 
2671
- return <span>{newData}</span>;
2841
+ return (
2842
+ <CellWithTooltip
2843
+ value={newData}
2844
+ columnType="option"
2845
+ >
2846
+ <span>{newData}</span>
2847
+ </CellWithTooltip>
2848
+ );
2672
2849
  } else {
2673
2850
  return null;
2674
2851
  }
@@ -2682,9 +2859,14 @@ const DataGrid = forwardRef(
2682
2859
  render: ({ data }) => {
2683
2860
  if (data) {
2684
2861
  return (
2685
- <span>
2686
- {column.placeholder}
2687
- </span>
2862
+ <CellWithTooltip
2863
+ value={column.placeholder}
2864
+ columnType="placeholder"
2865
+ >
2866
+ <span>
2867
+ {column.placeholder}
2868
+ </span>
2869
+ </CellWithTooltip>
2688
2870
  );
2689
2871
  } else {
2690
2872
  return null;
@@ -2772,7 +2954,14 @@ const DataGrid = forwardRef(
2772
2954
  typeof value === 'string' ||
2773
2955
  typeof value === 'number'
2774
2956
  ) {
2775
- return <span>{value}</span>;
2957
+ return (
2958
+ <CellWithTooltip
2959
+ value={value}
2960
+ columnType="relation"
2961
+ >
2962
+ <span>{value}</span>
2963
+ </CellWithTooltip>
2964
+ );
2776
2965
  } else {
2777
2966
  return null;
2778
2967
  }
@@ -2923,7 +3112,14 @@ const DataGrid = forwardRef(
2923
3112
  result += values.join('<br />');
2924
3113
  }
2925
3114
 
2926
- return <span>{parse(result)}</span>;
3115
+ return (
3116
+ <CellWithTooltip
3117
+ value={result}
3118
+ columnType="relationArray"
3119
+ >
3120
+ <span>{parse(result)}</span>
3121
+ </CellWithTooltip>
3122
+ );
2927
3123
  // }
2928
3124
  } else {
2929
3125
  let result = '';
@@ -2954,7 +3150,14 @@ const DataGrid = forwardRef(
2954
3150
  : '';
2955
3151
  }
2956
3152
 
2957
- return <span>{parse(result)}</span>;
3153
+ return (
3154
+ <CellWithTooltip
3155
+ value={result}
3156
+ columnType="relationArray"
3157
+ >
3158
+ <span>{parse(result)}</span>
3159
+ </CellWithTooltip>
3160
+ );
2958
3161
  }
2959
3162
 
2960
3163
  return null;
@@ -2967,9 +3170,14 @@ const DataGrid = forwardRef(
2967
3170
  render: ({ data }) => {
2968
3171
  if (data && data[column.id]) {
2969
3172
  return (
2970
- <span>
2971
- {parse(data[column.id])}
2972
- </span>
3173
+ <CellWithTooltip
3174
+ value={data[column.id]}
3175
+ columnType="richtext"
3176
+ >
3177
+ <span>
3178
+ {parse(data[column.id])}
3179
+ </span>
3180
+ </CellWithTooltip>
2973
3181
  );
2974
3182
  } else {
2975
3183
  return null;
@@ -2990,7 +3198,14 @@ const DataGrid = forwardRef(
2990
3198
  }
2991
3199
  });
2992
3200
 
2993
- return <span>{stage}</span>;
3201
+ return (
3202
+ <CellWithTooltip
3203
+ value={stage}
3204
+ columnType="stage"
3205
+ >
3206
+ <span>{stage}</span>
3207
+ </CellWithTooltip>
3208
+ );
2994
3209
  },
2995
3210
  };
2996
3211
  case 'time':
@@ -3014,7 +3229,14 @@ const DataGrid = forwardRef(
3014
3229
  : 'hh:mm A'
3015
3230
  );
3016
3231
 
3017
- return <span>{data}</span>;
3232
+ return (
3233
+ <CellWithTooltip
3234
+ value={data[column.id]}
3235
+ columnType="time"
3236
+ >
3237
+ <span>{data}</span>
3238
+ </CellWithTooltip>
3239
+ );
3018
3240
  } else {
3019
3241
  return null;
3020
3242
  }
@@ -3041,7 +3263,14 @@ const DataGrid = forwardRef(
3041
3263
  : 'DD-MM-YYYY hh:mm A'
3042
3264
  );
3043
3265
 
3044
- return <span>{data}</span>;
3266
+ return (
3267
+ <CellWithTooltip
3268
+ value={data[column.id]}
3269
+ columnType="timer"
3270
+ >
3271
+ <span>{data}</span>
3272
+ </CellWithTooltip>
3273
+ );
3045
3274
  } else {
3046
3275
  let shouldRenderButton = false;
3047
3276
 
@@ -3166,7 +3395,7 @@ const DataGrid = forwardRef(
3166
3395
  data-tooltip-id="system-tooltip"
3167
3396
  data-tooltip-content={`${column.label} Timer`}
3168
3397
  strokeWidth={2}
3169
- size={18}
3398
+ size={14}
3170
3399
  style={{
3171
3400
  color: 'white',
3172
3401
  }}
@@ -3467,14 +3696,21 @@ const DataGrid = forwardRef(
3467
3696
  render: ({ data }) => {
3468
3697
  if (data && data[column.id]) {
3469
3698
  return (
3470
- <span>
3471
- <a
3472
- href={data[column.id]}
3473
- target="_blank"
3474
- >
3475
- Link
3476
- </a>
3477
- </span>
3699
+ <CellWithTooltip
3700
+ value={data[column.id]}
3701
+ columnType="url"
3702
+ >
3703
+ <span>
3704
+ <a
3705
+ href={
3706
+ data[column.id]
3707
+ }
3708
+ target="_blank"
3709
+ >
3710
+ Link
3711
+ </a>
3712
+ </span>
3713
+ </CellWithTooltip>
3478
3714
  );
3479
3715
  } else {
3480
3716
  return null;
@@ -3606,7 +3842,14 @@ const DataGrid = forwardRef(
3606
3842
  }
3607
3843
 
3608
3844
  return (
3609
- <span>{parse(content)}</span>
3845
+ <CellWithTooltip
3846
+ value={data[column.id]}
3847
+ columnType="text"
3848
+ >
3849
+ <span>
3850
+ {parse(content)}
3851
+ </span>
3852
+ </CellWithTooltip>
3610
3853
  );
3611
3854
  } else {
3612
3855
  return null;
@@ -3840,7 +4083,7 @@ const DataGrid = forwardRef(
3840
4083
  setLimit(ajaxSetting.take);
3841
4084
  } else {
3842
4085
  const calculateRows = (height) =>
3843
- Math.floor((height - 100) / 40);
4086
+ Math.floor((height - 100) / 32);
3844
4087
 
3845
4088
  const rowCount = calculateRows(minHeight);
3846
4089
  setLimit(rowCount);
@@ -3952,7 +4195,7 @@ const DataGrid = forwardRef(
3952
4195
  }
3953
4196
  headerProps={headerProps}
3954
4197
  idProperty="id"
3955
- minRowHeight={40}
4198
+ minRowHeight={32}
3956
4199
  onFilterValueChange={(fv) => {
3957
4200
  handleFilterChange(fv, filterValue);
3958
4201
  }}
@@ -1112,8 +1112,8 @@ function Field({
1112
1112
  ? settings.options
1113
1113
  : options
1114
1114
  }
1115
- isSearchable={settings.isSearchable || true}
1116
- isCreatable={settings.isCreatable || false}
1115
+ isSearchable={settings.isSearchable ?? true}
1116
+ isCreatable={settings.isCreatable ?? false}
1117
1117
  dataOptions={dataOptions}
1118
1118
  style={style}
1119
1119
  />
@@ -25,7 +25,9 @@ const CustomOption = (props) => {
25
25
  <div className={styles.option}>
26
26
  <div className={styles.optionLabel}>{children}</div>
27
27
  {data.description && (
28
- <div className={styles.optionDescription}>{data.description}</div>
28
+ <div className={styles.optionDescription}>
29
+ {data.description}
30
+ </div>
29
31
  )}
30
32
  </div>
31
33
  </components.Option>
@@ -40,7 +42,9 @@ const CustomMultiValue = (props) => {
40
42
  <div className={styles.multiValue}>
41
43
  <div className={styles.multiValueLabel}>{children}</div>
42
44
  {data.description && (
43
- <div className={styles.multiValueDescription}>{data.description}</div>
45
+ <div className={styles.multiValueDescription}>
46
+ {data.description}
47
+ </div>
44
48
  )}
45
49
  </div>
46
50
  </components.MultiValue>
@@ -111,14 +115,14 @@ function MultiSelect({
111
115
  if (item.description) return item;
112
116
 
113
117
  // Try to find a matching option with description
114
- const matchingOption = selectOptions.find(opt =>
115
- opt.id === item.id || opt.value === item.id
118
+ const matchingOption = selectOptions.find(
119
+ (opt) => opt.id === item.id || opt.value === item.id
116
120
  );
117
121
 
118
122
  if (matchingOption && matchingOption.description) {
119
123
  return {
120
124
  ...item,
121
- description: matchingOption.description
125
+ description: matchingOption.description,
122
126
  };
123
127
  }
124
128
 
@@ -130,7 +134,11 @@ function MultiSelect({
130
134
  const enrichedItem = findMatchingOption(a);
131
135
  return {
132
136
  value: enrichedItem.id,
133
- label: enrichedItem.label || enrichedItem.name || enrichedItem.description || 'Unknown',
137
+ label:
138
+ enrichedItem.label ||
139
+ enrichedItem.name ||
140
+ enrichedItem.description ||
141
+ 'Unknown',
134
142
  // Make sure description is preserved
135
143
  description: enrichedItem.description || null,
136
144
  ...enrichedItem,
@@ -210,7 +218,7 @@ function MultiSelect({
210
218
  components={{
211
219
  SelectList,
212
220
  Option: CustomOption,
213
- MultiValue: CustomMultiValue
221
+ MultiValue: CustomMultiValue,
214
222
  }}
215
223
  options={selectOptions}
216
224
  menuPortalTarget={document.body}
@@ -301,21 +309,33 @@ function MultiSelect({
301
309
  padding: '2px 4px',
302
310
  minHeight: '30px',
303
311
  display: 'flex',
304
- flexDirection: 'column',
305
- alignItems: 'flex-start',
312
+ flexDirection: 'row',
313
+ alignItems: 'center',
314
+ backgroundColor: '#e2e8f0',
315
+ borderRadius: '4px',
306
316
  }),
307
317
  multiValueLabel: (base) => ({
308
318
  ...base,
309
- padding: '0',
319
+ padding: '0 4px 0 0',
310
320
  display: 'flex',
311
321
  flexDirection: 'column',
312
- width: '100%',
322
+ flex: '1',
323
+ minWidth: '0',
313
324
  }),
314
325
  multiValueRemove: (base) => ({
315
326
  ...base,
316
327
  padding: '0 4px',
317
- alignSelf: 'flex-start',
318
- marginTop: '2px',
328
+ display: 'flex',
329
+ alignItems: 'center',
330
+ justifyContent: 'center',
331
+ margin: '0',
332
+ borderRadius: '0 4px 4px 0',
333
+ backgroundColor: 'transparent',
334
+ transition: 'background-color 0.2s ease',
335
+ '&:hover': {
336
+ backgroundColor: '#dc2626',
337
+ color: 'white',
338
+ },
319
339
  }),
320
340
  }}
321
341
  value={selectValue}
@@ -21,19 +21,20 @@
21
21
  }
22
22
  }
23
23
 
24
- /* Clock button styles */
24
+ /* Clock button styles - Compact version */
25
25
  .clockButton {
26
26
  display: flex;
27
27
  align-items: center;
28
28
  justify-content: center;
29
- width: 40px;
30
- height: 40px;
31
- border-radius: 4px;
29
+ width: 28px;
30
+ height: 28px;
31
+ border-radius: 3px;
32
32
  background-color: #2b6cb0; /* Matching blue from screenshot */
33
33
  border: none;
34
34
  cursor: pointer;
35
35
  transition: all 0.2s ease;
36
36
  margin: 0 auto;
37
+ padding: 0;
37
38
 
38
39
  &:hover {
39
40
  background-color: #2563a0; /* Slightly darker blue on hover */
@@ -109,13 +110,22 @@
109
110
  justify-content: flex-start;
110
111
  align-items: center;
111
112
  gap: 0.1rem;
113
+ padding: 3px 0;
112
114
 
113
115
  span {
114
116
  display: flex;
115
117
  align-items: center;
116
118
  position: relative;
117
- width: 18px;
118
- height: 18px;
119
+ width: 20px;
120
+ height: 20px;
121
+ padding: 3px;
122
+ cursor: pointer;
123
+ border-radius: 3px;
124
+ transition: background-color 0.2s ease;
125
+
126
+ &:hover {
127
+ background-color: rgba(var(--primary-rgb, 59, 130, 246), 0.1);
128
+ }
119
129
 
120
130
  svg {
121
131
  width: 18px;
@@ -32,8 +32,12 @@
32
32
 
33
33
  .multiValue {
34
34
  display: flex;
35
- flex-direction: column;
36
- gap: 1px;
35
+ flex-direction: row;
36
+ align-items: center;
37
+ gap: 4px;
38
+ background-color: #e2e8f0;
39
+ border-radius: 4px;
40
+ padding: 2px 4px;
37
41
  }
38
42
 
39
43
  .multiValueLabel {
@@ -46,7 +50,12 @@
46
50
  color: rgba(0, 0, 0, 0.7); /* Darker color for better contrast */
47
51
  font-weight: 400;
48
52
  line-height: 1.1;
49
- background-color: rgba(255, 255, 255, 0.7); /* Semi-transparent white background */
53
+ background-color: rgba(
54
+ 255,
55
+ 255,
56
+ 255,
57
+ 0.7
58
+ ); /* Semi-transparent white background */
50
59
  padding: 1px 3px;
51
60
  border-radius: 2px;
52
61
  margin-top: 1px;
@@ -109,21 +118,34 @@
109
118
  padding: 2px 4px !important;
110
119
  min-height: 30px !important;
111
120
  display: flex !important;
112
- flex-direction: column !important;
113
- align-items: flex-start !important;
121
+ flex-direction: row !important;
122
+ align-items: center !important;
123
+ background-color: #e2e8f0 !important;
124
+ border-radius: 4px !important;
114
125
  }
115
126
 
116
127
  .visns-select__multi-value__label {
117
- padding: 0 !important;
128
+ padding: 0 4px 0 0 !important;
118
129
  display: flex !important;
119
130
  flex-direction: column !important;
120
- width: 100% !important;
131
+ flex: 1 !important;
132
+ min-width: 0 !important;
121
133
  }
122
134
 
123
135
  .visns-select__multi-value__remove {
124
136
  padding: 0 4px !important;
125
- align-self: flex-start !important;
126
- margin-top: 2px !important;
137
+ display: flex !important;
138
+ align-items: center !important;
139
+ justify-content: center !important;
140
+ margin: 0 !important;
141
+ border-radius: 0 4px 4px 0 !important;
142
+ background-color: transparent !important;
143
+ transition: background-color 0.2s ease !important;
144
+ }
145
+
146
+ .visns-select__multi-value__remove:hover {
147
+ background-color: #dc2626 !important;
148
+ color: white !important;
127
149
  }
128
150
 
129
151
  /* Fix for modals */
@@ -21,9 +21,15 @@
21
21
  border-radius: 0 !important;
22
22
  }
23
23
 
24
- /* Ensure consistent border radius for cells */
24
+ /* Ensure consistent border radius for cells and reduce padding for compact rows */
25
25
  .InovuaReactDataGrid__cell {
26
26
  border-radius: 0 !important;
27
+ padding: 6px 8px !important; /* Reduced from default padding */
28
+ }
29
+
30
+ /* Reduce header cell padding as well */
31
+ .InovuaReactDataGrid__header-cell {
32
+ padding: 6px 8px !important;
27
33
  }
28
34
 
29
35
  /* Fix for focus outline */
@@ -33,9 +39,9 @@
33
39
  box-shadow: 0 0 0 2px rgba(var(--primary-rgb, 59, 130, 246), 0.2) !important;
34
40
  }
35
41
 
36
- /* Checkbox column improvements for touch devices */
42
+ /* Checkbox column improvements for touch devices - Compact version */
37
43
  .InovuaReactDataGrid__cell[data-column-id='__checkbox-column'] {
38
- padding: 8px 12px !important;
44
+ padding: 6px 8px !important; /* Reduced padding for compact rows */
39
45
  min-width: 60px !important;
40
46
  width: 60px !important;
41
47
  max-width: 60px !important;
@@ -92,9 +98,9 @@
92
98
  background-color: rgba(var(--primary-rgb, 59, 130, 246), 0.1) !important;
93
99
  }
94
100
 
95
- /* Header checkbox improvements */
101
+ /* Header checkbox improvements - Compact version */
96
102
  .InovuaReactDataGrid__header-cell[data-column-id='__checkbox-column'] {
97
- padding: 8px 12px !important;
103
+ padding: 6px 8px !important; /* Reduced padding for compact rows */
98
104
  min-width: 60px !important;
99
105
  width: 60px !important;
100
106
  max-width: 60px !important;
@@ -139,13 +145,13 @@
139
145
  background-color: inherit !important;
140
146
  }
141
147
 
142
- /* Mobile-specific improvements */
148
+ /* Mobile-specific improvements - Compact version */
143
149
  @media (max-width: 768px) {
144
150
  .InovuaReactDataGrid__cell[data-column-id='__checkbox-column'] {
145
151
  min-width: 70px !important;
146
152
  width: 70px !important;
147
153
  max-width: 70px !important;
148
- padding: 12px 16px !important;
154
+ padding: 8px 12px !important; /* Reduced padding for compact rows */
149
155
  }
150
156
 
151
157
  .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
@@ -168,7 +174,7 @@
168
174
  min-width: 70px !important;
169
175
  width: 70px !important;
170
176
  max-width: 70px !important;
171
- padding: 12px 16px !important;
177
+ padding: 8px 12px !important; /* Reduced padding for compact rows */
172
178
  }
173
179
 
174
180
  .InovuaReactDataGrid__header-cell[data-column-id='__checkbox-column']
@@ -178,10 +184,10 @@
178
184
  }
179
185
  }
180
186
 
181
- /* Touch device specific improvements */
187
+ /* Touch device specific improvements - Compact version */
182
188
  @media (hover: none) and (pointer: coarse) {
183
189
  .InovuaReactDataGrid__cell[data-column-id='__checkbox-column'] {
184
- padding: 16px !important;
190
+ padding: 12px !important; /* Reduced padding for compact rows */
185
191
  }
186
192
 
187
193
  .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
@@ -0,0 +1,76 @@
1
+ import React, { useState } from 'react';
2
+ import MultiSelect from '../components/crm/MultiSelect';
3
+
4
+ function MultiSelectStylingTest() {
5
+ const [selectedValues, setSelectedValues] = useState([]);
6
+
7
+ const options = [
8
+ { id: 1, label: 'Customer Service Representative' },
9
+ { id: 2, label: 'Dashboard Administrator' },
10
+ { id: 3, label: 'Project Manager' },
11
+ { id: 4, label: 'Technical Support Specialist' },
12
+ { id: 5, label: 'Sales Representative' },
13
+ { id: 6, label: 'Quality Assurance Analyst' },
14
+ { id: 7, label: 'Business Development Manager' },
15
+ { id: 8, label: 'Software Engineer' }
16
+ ];
17
+
18
+ const handleChange = (values) => {
19
+ setSelectedValues(values);
20
+ };
21
+
22
+ return (
23
+ <div style={{ padding: '20px', maxWidth: '600px', margin: '0 auto' }}>
24
+ <h1>MultiSelect Styling Test</h1>
25
+ <p>This test verifies that the delete/cross icons appear inline with the selected item text, not on a new line.</p>
26
+
27
+ <div style={{ marginBottom: '20px' }}>
28
+ <h3>Test Case: Multi-Select with Long Labels</h3>
29
+ <p>Select multiple items to test the inline styling of delete icons:</p>
30
+
31
+ <MultiSelect
32
+ settings={{
33
+ id: 'stylingTest',
34
+ multi: true
35
+ }}
36
+ multi={true}
37
+ options={options}
38
+ inputValue={selectedValues}
39
+ onChange={handleChange}
40
+ placeholder="Select multiple roles..."
41
+ />
42
+ </div>
43
+
44
+ <div style={{ marginTop: '20px', padding: '15px', backgroundColor: '#f5f5f5', borderRadius: '8px' }}>
45
+ <h3>Expected Behavior:</h3>
46
+ <ul>
47
+ <li>✅ Delete icons should appear on the same line as the text</li>
48
+ <li>✅ Selected items should have a consistent height</li>
49
+ <li>✅ Delete icons should be clickable and remove the item</li>
50
+ <li>✅ Hover effect should work on delete icons</li>
51
+ <li>✅ Multiple selections should wrap properly without excessive height</li>
52
+ </ul>
53
+ </div>
54
+
55
+ <div style={{ marginTop: '20px' }}>
56
+ <h3>Selected Values:</h3>
57
+ <pre style={{ backgroundColor: '#fff', padding: '10px', borderRadius: '4px', border: '1px solid #ddd' }}>
58
+ {JSON.stringify(selectedValues, null, 2)}
59
+ </pre>
60
+ </div>
61
+
62
+ <div style={{ marginTop: '20px', fontSize: '14px', color: '#666' }}>
63
+ <p><strong>Testing Instructions:</strong></p>
64
+ <ol>
65
+ <li>Select 2-3 items from the dropdown</li>
66
+ <li>Verify that delete icons (×) appear next to each selected item on the same line</li>
67
+ <li>Hover over delete icons to see the red background effect</li>
68
+ <li>Click delete icons to remove items</li>
69
+ <li>Check that the component height doesn't grow unnecessarily</li>
70
+ </ol>
71
+ </div>
72
+ </div>
73
+ );
74
+ }
75
+
76
+ export default MultiSelectStylingTest;