@visns-studio/visns-components 5.14.8 → 5.14.9

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
@@ -50,6 +50,7 @@ VISNS Components is a React-based UI component library that provides a set of re
50
50
  - **Download** - File download functionality
51
51
  - **Loader** - Loading indicators
52
52
  - **Notification** - Toast notifications
53
+ - **StandardModal** - Reusable modal component with consistent styling
53
54
  - **Call Popup** - Modal dialogs
54
55
  - **QR Code** - QR code generation and printing
55
56
 
@@ -2312,6 +2313,64 @@ The QrCode component fetches QR code data from the specified URL and displays it
2312
2313
  }
2313
2314
  ```
2314
2315
 
2316
+ #### StandardModal
2317
+
2318
+ A reusable modal component that standardizes the look and feel across all components. Uses the same styling as GenericIndex, GenericDetail, and DataGrid modals, providing consistent modal behavior throughout the application.
2319
+
2320
+ ```jsx
2321
+ import { StandardModal } from '@visns-studio/visns-components';
2322
+
2323
+ // Basic usage (for custom content with header)
2324
+ <StandardModal
2325
+ isOpen={showModal}
2326
+ onClose={() => setShowModal(false)}
2327
+ title="Modal Title"
2328
+ showHeader={true}
2329
+ >
2330
+ <div>Your custom content here</div>
2331
+ </StandardModal>
2332
+
2333
+ // Usage with Form components (no header - Form provides its own)
2334
+ <StandardModal
2335
+ isOpen={modalShow}
2336
+ onClose={modalClose}
2337
+ closeOnDocumentClick={false}
2338
+ verticalAlign="top"
2339
+ >
2340
+ <Form
2341
+ closeModal={modalClose}
2342
+ formSettings={formData}
2343
+ // ... other Form props
2344
+ />
2345
+ </StandardModal>
2346
+ ```
2347
+
2348
+ **Props:**
2349
+
2350
+ - `isOpen` (boolean): Whether the modal is open
2351
+ - `onClose` (function): Function to call when modal should close
2352
+ - `title` (string): Modal title (only used if showHeader is true)
2353
+ - `children` (ReactNode): Modal content
2354
+ - `size` (string): Modal size - 'small' (400px), 'medium' (90vw, default), 'large' (95vw)
2355
+ - `closeOnDocumentClick` (boolean): Whether to close on outside click (default: true)
2356
+ - `closeOnEscape` (boolean): Whether to close on escape key (default: true)
2357
+ - `verticalAlign` (string): Vertical alignment - 'center' (default) or 'top'
2358
+ - `showHeader` (boolean): Whether to show modal header (default: false)
2359
+ - `customStyles` (object): Custom styles to override defaults
2360
+
2361
+ **Size Options:**
2362
+
2363
+ - **Small**: 400px width, ideal for simple dialogs and confirmations
2364
+ - **Medium** (default): 90vw width with 600px min and 1400px max, matches original modal sizing
2365
+ - **Large**: 95vw width with 800px min and 1600px max, for very large content
2366
+
2367
+ **Important Notes:**
2368
+
2369
+ - When wrapping Form components, use `showHeader={false}` (default) since Form components render their own headers
2370
+ - When creating custom modals with simple content, use `showHeader={true}` to display the title and close button
2371
+ - The component automatically handles responsive sizing and vertical alignment
2372
+ - Uses the same CSS classes as existing modals (modalwrap, modal, modal__header, modal__content)
2373
+
2315
2374
  ### Enhanced OCR Template Component
2316
2375
 
2317
2376
  #### OcrTemplateEnhanced (v5.14.0+)
package/package.json CHANGED
@@ -88,7 +88,7 @@
88
88
  "react-dom": "^17.0.0 || ^18.0.0"
89
89
  },
90
90
  "name": "@visns-studio/visns-components",
91
- "version": "5.14.8",
91
+ "version": "5.14.9",
92
92
  "description": "Various packages to assist in the development of our Custom Applications.",
93
93
  "main": "src/index.js",
94
94
  "files": [
@@ -30,7 +30,6 @@ import React, {
30
30
  import { useNavigate } from 'react-router-dom';
31
31
  import debounce from 'lodash.debounce';
32
32
  import moment from 'moment';
33
- import Popup from 'reactjs-popup';
34
33
  import 'reactjs-popup/dist/index.css';
35
34
  import parse from 'html-react-parser';
36
35
  import { saveAs } from 'file-saver';
@@ -80,6 +79,7 @@ import CustomFetch from './Fetch';
80
79
  import Download from './Download';
81
80
  import Form from './Form';
82
81
  import CellWithTooltip from './cells/CellWithTooltip';
82
+ import StandardModal from './generic/StandardModal';
83
83
  import DataGridSearch from './controls/DataGridSearch';
84
84
  import AutoRefreshControls from './controls/AutoRefreshControls';
85
85
  import AudioPlayer from './controls/AudioPlayer';
@@ -3714,55 +3714,32 @@ const DataGrid = forwardRef(
3714
3714
  )}
3715
3715
 
3716
3716
  <AudioPlayer audioSource={audioSource} />
3717
- <Popup
3718
- open={modalShow}
3717
+ <StandardModal
3718
+ isOpen={modalShow}
3719
3719
  onClose={modalClose}
3720
+ title={formData.title || 'Edit Data'}
3720
3721
  closeOnDocumentClick={false}
3721
- contentStyle={{
3722
- width: windowWidth < 768 ? '95vw' : '90vw',
3723
- minWidth: windowWidth < 768 ? '95vw' : '600px',
3724
- maxWidth: '1400px',
3725
- ...(formData.verticalAlign === 'top' && {
3726
- position: 'fixed',
3727
- top: '5vh',
3728
- left: '50%',
3729
- transform: 'translateX(-50%)',
3730
- margin: '0',
3731
- }),
3732
- }}
3733
- nested
3722
+ verticalAlign={formData.verticalAlign || 'center'}
3734
3723
  >
3735
- <div
3736
- className={`${styles.modalwrap} ${
3737
- styles['top--modal']
3738
- } ${styles.modalWide} ${
3739
- formData.verticalAlign === 'top'
3740
- ? 'modal-top-aligned'
3741
- : ''
3742
- }`}
3743
- >
3744
- <div className={styles.modal}>
3745
- <Form
3746
- ajaxSetting={ajaxSetting}
3747
- api={api}
3748
- closeModal={modalClose}
3749
- columnId={formId}
3750
- fetchTable={handleReload}
3751
- formSettings={formData}
3752
- formType={formType}
3753
- gridRef={gridRef}
3754
- mapbox={mapbox}
3755
- modalOpen={modalOpen}
3756
- paramValue={paramValue}
3757
- style={style}
3758
- updateForm={setFormData}
3759
- userProfile={userProfile}
3760
- bulkEditMode={bulkEditMode}
3761
- bulkEditGroupData={bulkEditGroupData}
3762
- />
3763
- </div>
3764
- </div>
3765
- </Popup>
3724
+ <Form
3725
+ ajaxSetting={ajaxSetting}
3726
+ api={api}
3727
+ closeModal={modalClose}
3728
+ columnId={formId}
3729
+ fetchTable={handleReload}
3730
+ formSettings={formData}
3731
+ formType={formType}
3732
+ gridRef={gridRef}
3733
+ mapbox={mapbox}
3734
+ modalOpen={modalOpen}
3735
+ paramValue={paramValue}
3736
+ style={style}
3737
+ updateForm={setFormData}
3738
+ userProfile={userProfile}
3739
+ bulkEditMode={bulkEditMode}
3740
+ bulkEditGroupData={bulkEditGroupData}
3741
+ />
3742
+ </StandardModal>
3766
3743
  <GalleryModal
3767
3744
  modalGalleryShow={modalGalleryShow}
3768
3745
  modalGalleryClose={modalGalleryClose}
@@ -4,15 +4,15 @@ import { Link, useNavigate } from 'react-router-dom';
4
4
  import { ResponsiveBar } from '@nivo/bar';
5
5
  import { ResponsiveLine } from '@nivo/line';
6
6
  import { ResponsivePie } from '@nivo/pie';
7
- import Popup from 'reactjs-popup';
8
7
  import dayjs from 'dayjs';
9
8
  import parse from 'html-react-parser';
10
9
  import { createSwapy } from 'swapy';
11
- import { X, Minus, Plus, Star, Trash2, Eye, RotateCcw, Clock, Database, BarChart3, AlertCircle } from 'lucide-react';
10
+ import { Minus, Plus, Star, Trash2, Eye, RotateCcw, Clock, Database, BarChart3, AlertCircle } from 'lucide-react';
12
11
  import { confirmDialog } from '../utils/ConfirmDialog';
13
12
 
14
13
  import CustomFetch from '../Fetch';
15
14
  import MultiSelect from '../MultiSelect';
15
+ import StandardModal from './StandardModal';
16
16
 
17
17
  import styles from '../styles/GenericDashboard.module.scss';
18
18
 
@@ -1961,63 +1961,49 @@ function GenericDashboard({ setting, userProfile, dynamicDashboard = false }) {
1961
1961
  )}
1962
1962
  <div className={styles.grid} ref={container}>
1963
1963
  {renderWidgets()}
1964
- <Popup
1965
- open={showAddWidgetModal}
1964
+ <StandardModal
1965
+ isOpen={showAddWidgetModal}
1966
1966
  onClose={() => setShowAddWidgetModal(false)}
1967
- modal
1967
+ title="Add a Widget"
1968
+ showHeader={true}
1968
1969
  >
1969
- <div className="modalwrap">
1970
- <div className={`${styles.addWidgetPopup} modal`}>
1971
- <div className="modal__header">
1972
- <h1>Add a Widget</h1>
1973
- <button
1974
- className="modal__close"
1975
- onClick={() => setShowAddWidgetModal(false)}
1976
- >
1977
- <X strokeWidth={1} size={24} />
1978
- </button>
1979
- </div>
1980
- <div className="modal__content">
1981
- {availableWidgets.length > 0 ? (
1982
- <div>
1983
- <select
1984
- value={selectedAddWidgetId}
1985
- onChange={(e) =>
1986
- setSelectedAddWidgetId(
1987
- e.target.value
1988
- )
1989
- }
1990
- className={styles.addWidgetSelect}
1991
- >
1992
- <option value="">
1993
- -- Select a widget --
1994
- </option>
1995
- {availableWidgets.map((widget) => (
1996
- <option
1997
- key={widget.id}
1998
- value={widget.id}
1999
- >
2000
- {widget.title}
2001
- </option>
2002
- ))}
2003
- </select>
2004
- <button
2005
- className={styles.addWidgetButton}
2006
- onClick={handleAddWidget}
2007
- disabled={!selectedAddWidgetId}
2008
- >
2009
- Add
2010
- </button>
2011
- </div>
2012
- ) : (
2013
- <div className={styles.noWidgetsMessage}>
2014
- No more widgets available
2015
- </div>
2016
- )}
2017
- </div>
1970
+ {availableWidgets.length > 0 ? (
1971
+ <div>
1972
+ <select
1973
+ value={selectedAddWidgetId}
1974
+ onChange={(e) =>
1975
+ setSelectedAddWidgetId(
1976
+ e.target.value
1977
+ )
1978
+ }
1979
+ className={styles.addWidgetSelect}
1980
+ >
1981
+ <option value="">
1982
+ -- Select a widget --
1983
+ </option>
1984
+ {availableWidgets.map((widget) => (
1985
+ <option
1986
+ key={widget.id}
1987
+ value={widget.id}
1988
+ >
1989
+ {widget.title}
1990
+ </option>
1991
+ ))}
1992
+ </select>
1993
+ <button
1994
+ className={styles.addWidgetButton}
1995
+ onClick={handleAddWidget}
1996
+ disabled={!selectedAddWidgetId}
1997
+ >
1998
+ ADD
1999
+ </button>
2018
2000
  </div>
2019
- </div>
2020
- </Popup>
2001
+ ) : (
2002
+ <div className={styles.noWidgetsMessage}>
2003
+ No more widgets available
2004
+ </div>
2005
+ )}
2006
+ </StandardModal>
2021
2007
  </div>
2022
2008
  </>
2023
2009
  );
@@ -39,7 +39,6 @@ import { Calendar, momentLocalizer } from 'react-big-calendar';
39
39
  import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop';
40
40
  import moment from 'moment';
41
41
  import parse from 'html-react-parser';
42
- import Popup from 'reactjs-popup';
43
42
  import { motion } from 'framer-motion';
44
43
  import Dropzone from 'react-dropzone';
45
44
  import truncate from 'truncate';
@@ -79,6 +78,7 @@ import MergeEntity from '../MergeEntity';
79
78
  import MultiSelect from '../MultiSelect';
80
79
  import QrCode from '../QrCode';
81
80
  import StagePopupModal from './StagePopupModal';
81
+ import StandardModal from './StandardModal';
82
82
  import Table from '../DataGrid';
83
83
  import TableFilter from '../TableFilter';
84
84
  import CategorizedDropZone from '../CategorizedDropZone';
@@ -3831,51 +3831,29 @@ function GenericDetail({
3831
3831
  </div>
3832
3832
  </div>
3833
3833
 
3834
- <Popup
3835
- open={modalShow}
3834
+ <StandardModal
3835
+ isOpen={modalShow}
3836
3836
  onClose={modalClose}
3837
+ title={formData.title || 'Edit Details'}
3837
3838
  closeOnDocumentClick={false}
3838
- contentStyle={{
3839
- width: windowWidth < 768 ? '95vw' : '90vw',
3840
- minWidth: windowWidth < 768 ? '95vw' : '600px',
3841
- maxWidth: '1400px',
3842
- ...(formData.verticalAlign === 'top' && {
3843
- position: 'fixed',
3844
- top: '5vh',
3845
- left: '50%',
3846
- transform: 'translateX(-50%)',
3847
- margin: '0',
3848
- }),
3849
- }}
3839
+ verticalAlign={formData.verticalAlign || 'center'}
3850
3840
  >
3851
- <div
3852
- className={`${styles.modalwrap} ${
3853
- styles['top--modal']
3854
- } ${styles.modalWide} ${
3855
- formData.verticalAlign === 'top'
3856
- ? 'modal-top-aligned'
3857
- : ''
3858
- }`}
3859
- >
3860
- <div className={styles.modal}>
3861
- <Form
3862
- closeModal={modalClose}
3863
- columnId={formId}
3864
- fetchTable={handleReload}
3865
- formSettings={formData}
3866
- formType={formType}
3867
- style={userProfile?.settings?.style || {}}
3868
- updateForm={setFormData}
3869
- userProfile={userProfile}
3870
- paramValue={
3871
- routeParams[urlParam]
3872
- ? routeParams[urlParam]
3873
- : 0
3874
- }
3875
- />
3876
- </div>
3877
- </div>
3878
- </Popup>
3841
+ <Form
3842
+ closeModal={modalClose}
3843
+ columnId={formId}
3844
+ fetchTable={handleReload}
3845
+ formSettings={formData}
3846
+ formType={formType}
3847
+ style={userProfile?.settings?.style || {}}
3848
+ updateForm={setFormData}
3849
+ userProfile={userProfile}
3850
+ paramValue={
3851
+ routeParams[urlParam]
3852
+ ? routeParams[urlParam]
3853
+ : 0
3854
+ }
3855
+ />
3856
+ </StandardModal>
3879
3857
 
3880
3858
  <StagePopupModal
3881
3859
  show={showStagePopup}
@@ -7,7 +7,6 @@ import { saveAs } from 'file-saver';
7
7
  import _ from 'lodash';
8
8
  import parse from 'html-react-parser';
9
9
  import moment from 'moment';
10
- import Popup from 'reactjs-popup';
11
10
 
12
11
  import CustomFetch from '../Fetch';
13
12
  import Download from '../Download';
@@ -16,6 +15,7 @@ import Table from '../DataGrid';
16
15
  import TableFilter from '../TableFilter';
17
16
  import GenericGrid from './GenericGrid';
18
17
  import GenericReportForm from './GenericReportForm';
18
+ import StandardModal from './StandardModal';
19
19
 
20
20
  import styles from '../styles/GenericIndex.module.scss';
21
21
 
@@ -1006,44 +1006,22 @@ function GenericIndex({
1006
1006
  </div>
1007
1007
 
1008
1008
  {/* Custom Action Modal */}
1009
- <Popup
1010
- open={customActionModalShow}
1009
+ <StandardModal
1010
+ isOpen={customActionModalShow}
1011
1011
  onClose={closeCustomActionModal}
1012
+ title={customActionFormData.title || 'Custom Action'}
1012
1013
  closeOnDocumentClick={false}
1013
- contentStyle={{
1014
- width: windowWidth < 768 ? '95vw' : '90vw',
1015
- minWidth: windowWidth < 768 ? '95vw' : '600px',
1016
- maxWidth: '1400px',
1017
- ...(customActionFormData.verticalAlign === 'top' && {
1018
- position: 'fixed',
1019
- top: '5vh',
1020
- left: '50%',
1021
- transform: 'translateX(-50%)',
1022
- margin: '0',
1023
- }),
1024
- }}
1014
+ verticalAlign={customActionFormData.verticalAlign || 'center'}
1025
1015
  >
1026
- <div
1027
- className={`${styles.modalwrap} ${
1028
- styles['top--modal']
1029
- } ${styles.modalWide} ${
1030
- customActionFormData.verticalAlign === 'top'
1031
- ? 'modal-top-aligned'
1032
- : ''
1033
- }`}
1034
- >
1035
- <div className={styles.modal}>
1036
- <Form
1037
- closeModal={closeCustomActionModal}
1038
- formSettings={customActionFormData}
1039
- formType="create"
1040
- fetchTable={handleCustomActionSubmit}
1041
- style={userProfile?.settings?.style || {}}
1042
- userProfile={userProfile}
1043
- />
1044
- </div>
1045
- </div>
1046
- </Popup>
1016
+ <Form
1017
+ closeModal={closeCustomActionModal}
1018
+ formSettings={customActionFormData}
1019
+ formType="create"
1020
+ fetchTable={handleCustomActionSubmit}
1021
+ style={userProfile?.settings?.style || {}}
1022
+ userProfile={userProfile}
1023
+ />
1024
+ </StandardModal>
1047
1025
  </>
1048
1026
  );
1049
1027
  }
@@ -0,0 +1,119 @@
1
+ import React from 'react';
2
+ import Popup from 'reactjs-popup';
3
+ import { X } from 'lucide-react';
4
+ import formStyles from '../styles/Form.module.scss';
5
+
6
+ /**
7
+ * StandardModal Component
8
+ *
9
+ * A reusable modal component that standardizes the look and feel across all components.
10
+ * Uses the same styles as GenericIndex, GenericDetail, and DataGrid via Form.module.scss.
11
+ *
12
+ * @param {Object} props
13
+ * @param {boolean} props.isOpen - Whether the modal is open
14
+ * @param {Function} props.onClose - Function to call when modal should close
15
+ * @param {string} props.title - Modal title (only used if showHeader is true)
16
+ * @param {React.ReactNode} props.children - Modal content
17
+ * @param {string} props.size - Modal size: 'small', 'medium', 'large' (default: 'medium')
18
+ * @param {boolean} props.closeOnDocumentClick - Whether to close on outside click (default: true)
19
+ * @param {boolean} props.closeOnEscape - Whether to close on escape key (default: true)
20
+ * @param {string} props.verticalAlign - Vertical alignment: 'center', 'top' (default: 'center')
21
+ * @param {boolean} props.showHeader - Whether to show modal header (default: false, because Form components have their own headers)
22
+ * @param {Object} props.customStyles - Custom styles to override defaults
23
+ */
24
+ const StandardModal = ({
25
+ isOpen,
26
+ onClose,
27
+ title,
28
+ children,
29
+ size = 'medium',
30
+ closeOnDocumentClick = true,
31
+ closeOnEscape = true,
32
+ verticalAlign = 'center',
33
+ showHeader = false,
34
+ customStyles = {}
35
+ }) => {
36
+ // Apply vertical alignment class if needed
37
+ const getModalClasses = () => {
38
+ let classes = formStyles.modal;
39
+ if (verticalAlign === 'top') {
40
+ classes += ' modal-top-aligned';
41
+ }
42
+ return classes;
43
+ };
44
+
45
+ // Get size-based styles for the popup content
46
+ const getContentStyle = () => {
47
+ const baseStyle = {
48
+ ...customStyles.modal
49
+ };
50
+
51
+ // Apply size-based styles
52
+ switch (size) {
53
+ case 'small':
54
+ return {
55
+ ...baseStyle,
56
+ width: '400px',
57
+ minWidth: '400px',
58
+ maxWidth: '600px'
59
+ };
60
+ case 'large':
61
+ return {
62
+ ...baseStyle,
63
+ width: '95vw',
64
+ minWidth: '800px',
65
+ maxWidth: '1600px'
66
+ };
67
+ case 'medium':
68
+ default:
69
+ // Use the same dimensions as modalWide class (original modal sizing)
70
+ return {
71
+ ...baseStyle,
72
+ width: '90vw',
73
+ minWidth: '600px',
74
+ maxWidth: '1400px'
75
+ };
76
+ }
77
+ };
78
+
79
+ return (
80
+ <Popup
81
+ open={isOpen}
82
+ onClose={onClose}
83
+ modal
84
+ closeOnDocumentClick={closeOnDocumentClick}
85
+ closeOnEscape={closeOnEscape}
86
+ contentStyle={getContentStyle()}
87
+ >
88
+ <div className={formStyles.modalwrap}>
89
+ <div
90
+ className={getModalClasses()}
91
+ >
92
+ {showHeader && (
93
+ <div className={formStyles.modal__header} style={customStyles.header}>
94
+ <h1 style={customStyles.title}>{title}</h1>
95
+ <button
96
+ className={formStyles.modal__close}
97
+ onClick={onClose}
98
+ style={customStyles.closeButton}
99
+ type="button"
100
+ aria-label="Close modal"
101
+ >
102
+ <X strokeWidth={1} size={24} />
103
+ </button>
104
+ </div>
105
+ )}
106
+ {showHeader ? (
107
+ <div className={formStyles.modal__content} style={customStyles.content}>
108
+ {children}
109
+ </div>
110
+ ) : (
111
+ children
112
+ )}
113
+ </div>
114
+ </div>
115
+ </Popup>
116
+ );
117
+ };
118
+
119
+ export default StandardModal;
package/src/index.js CHANGED
@@ -72,6 +72,7 @@ import GenericSort from './components/generic/GenericSort';
72
72
  import NotificationList from './components/generic/NotificationList';
73
73
  import OcrTemplateEnhanced from './components/generic/OcrTemplateEnhanced';
74
74
  import StagePopupModal from './components/generic/StagePopupModal';
75
+ import StandardModal from './components/generic/StandardModal';
75
76
 
76
77
  /** Proposal Components */
77
78
  import ProposalTemplateSectionManager from './components/proposal/ProposalTemplateSectionManager';
@@ -145,6 +146,7 @@ export {
145
146
  showConfirmDialog,
146
147
  SortableList,
147
148
  StagePopupModal,
149
+ StandardModal,
148
150
  Table,
149
151
  TableFilter,
150
152
  VariableInserter,