@visns-studio/visns-components 5.6.0 → 5.6.2

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/package.json CHANGED
@@ -84,7 +84,7 @@
84
84
  "react-dom": "^17.0.0 || ^18.0.0"
85
85
  },
86
86
  "name": "@visns-studio/visns-components",
87
- "version": "5.6.0",
87
+ "version": "5.6.2",
88
88
  "description": "Various packages to assist in the development of our Custom Applications.",
89
89
  "main": "src/index.js",
90
90
  "files": [
@@ -5,6 +5,18 @@ import Select, { createFilter } from 'react-select';
5
5
  import CreatableSelect from 'react-select/creatable';
6
6
  import styles from './styles/MultiSelect.module.scss';
7
7
 
8
+ // Helper function to detect if the device is a mobile/touch device
9
+ const isTouchDevice = () => {
10
+ // Check if window is defined (to avoid SSR issues)
11
+ if (typeof window === 'undefined') return false;
12
+
13
+ return (
14
+ 'ontouchstart' in window ||
15
+ (navigator && navigator.maxTouchPoints > 0) ||
16
+ (navigator && navigator.msMaxTouchPoints > 0)
17
+ );
18
+ };
19
+
8
20
  function MultiSelect({
9
21
  className,
10
22
  inputValue = [], // Default to an empty array
@@ -20,6 +32,15 @@ function MultiSelect({
20
32
  const [selectOptions, setSelectOptions] = useState([]);
21
33
  const [selectValue, setSelectValue] = useState([]);
22
34
  const prevInputValueRef = useRef();
35
+ const [menuPlacement, setMenuPlacement] = useState('bottom');
36
+
37
+ // Detect if it's a touch device and set menu placement accordingly
38
+ // This helps prevent the dropdown from being hidden by the onscreen keyboard on mobile devices
39
+ useEffect(() => {
40
+ if (isTouchDevice()) {
41
+ setMenuPlacement('top'); // Open dropdown above the input on touch devices
42
+ }
43
+ }, []);
23
44
 
24
45
  useEffect(() => {
25
46
  const _options =
@@ -99,6 +120,24 @@ function MultiSelect({
99
120
 
100
121
  const SelectComponent = isCreatable ? CreatableSelect : Select;
101
122
 
123
+ // Handle menu opening to ensure it's visible on mobile
124
+ const handleMenuOpen = () => {
125
+ if (isTouchDevice()) {
126
+ // Give a small delay for the menu to render
127
+ setTimeout(() => {
128
+ const menuElement = document.querySelector(
129
+ '.visns-select__menu'
130
+ );
131
+ if (menuElement) {
132
+ menuElement.scrollIntoView({
133
+ behavior: 'smooth',
134
+ block: 'nearest',
135
+ });
136
+ }
137
+ }, 50);
138
+ }
139
+ };
140
+
102
141
  return (
103
142
  <SelectComponent
104
143
  closeMenuOnSelect={!multi || settings.limit === 1}
@@ -109,6 +148,8 @@ function MultiSelect({
109
148
  components={{ SelectList }}
110
149
  options={selectOptions}
111
150
  menuPortalTarget={document.body}
151
+ menuPlacement={menuPlacement}
152
+ onMenuOpen={handleMenuOpen}
112
153
  onChange={(inputValue, action) => {
113
154
  // Special handling for limit=1: keep only the most recent selection
114
155
  if (
@@ -135,6 +176,10 @@ function MultiSelect({
135
176
  ...provided,
136
177
  zIndex: 99999,
137
178
  }),
179
+ option: (base) => ({
180
+ ...base,
181
+ padding: isTouchDevice() ? '12px 15px' : base.padding, // Larger padding for touch devices
182
+ }),
138
183
  menuPortal: (base) => ({
139
184
  ...base,
140
185
  zIndex: 99999,
@@ -144,7 +189,9 @@ function MultiSelect({
144
189
  minHeight:
145
190
  style && style.multi_select_height
146
191
  ? style.multi_select_height
147
- : '50px',
192
+ : isTouchDevice()
193
+ ? '44px'
194
+ : '50px', // Apple's recommended minimum touch target size
148
195
  height: 'auto', // Allow height to grow with content
149
196
  }),
150
197
  valueContainer: (base) => ({
@@ -9,6 +9,7 @@ function TableFilter({
9
9
  type,
10
10
  }) {
11
11
  const [visibleChildren, setVisibleChildren] = useState(null);
12
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
12
13
  const isFirstRender = useRef(true);
13
14
 
14
15
  useEffect(() => {
@@ -116,6 +117,9 @@ function TableFilter({
116
117
  if (collapsible && filtertype === 'parent') {
117
118
  setVisibleChildren(id);
118
119
  }
120
+
121
+ // Close mobile menu after selection on small screens
122
+ setMobileMenuOpen(false);
119
123
  };
120
124
 
121
125
  const renderContent = (d) => {
@@ -184,14 +188,40 @@ function TableFilter({
184
188
  }
185
189
  };
186
190
 
191
+ const toggleMobileMenu = () => {
192
+ setMobileMenuOpen(!mobileMenuOpen);
193
+ };
194
+
195
+ // Find active filter for mobile display
196
+ const activeFilter = filters.find((filter) => filter.show === true);
197
+ const activeLabel = activeFilter ? activeFilter.label : 'Select Option';
198
+
187
199
  return (
188
- <ul className={styles.tableFilter}>
189
- {filters.map((item, key) => (
190
- <React.Fragment key={'table-filter-' + key}>
191
- {renderContent(item)}
192
- </React.Fragment>
193
- ))}
194
- </ul>
200
+ <div className={styles.tableFilterContainer}>
201
+ <div className={styles.mobileToggle} onClick={toggleMobileMenu}>
202
+ <span className={styles.activeFilterLabel}>{activeLabel}</span>
203
+ <span
204
+ className={`${styles.mobileMenuIcon} ${
205
+ mobileMenuOpen ? styles.open : ''
206
+ }`}
207
+ >
208
+ <span></span>
209
+ <span></span>
210
+ <span></span>
211
+ </span>
212
+ </div>
213
+ <ul
214
+ className={`${styles.tableFilter} ${
215
+ mobileMenuOpen ? styles.mobileOpen : ''
216
+ }`}
217
+ >
218
+ {filters.map((item, key) => (
219
+ <React.Fragment key={'table-filter-' + key}>
220
+ {renderContent(item)}
221
+ </React.Fragment>
222
+ ))}
223
+ </ul>
224
+ </div>
195
225
  );
196
226
  }
197
227
 
@@ -40,6 +40,17 @@
40
40
 
41
41
  .navwrap {
42
42
  flex: 1;
43
+ overflow-x: auto;
44
+ -webkit-overflow-scrolling: touch; /* For smooth scrolling on iOS */
45
+
46
+ /* Hide scrollbar for most browsers but keep functionality */
47
+ scrollbar-width: none; /* Firefox */
48
+ -ms-overflow-style: none; /* IE and Edge */
49
+
50
+ /* Hide scrollbar for Chrome/Safari/Opera */
51
+ &::-webkit-scrollbar {
52
+ display: none;
53
+ }
43
54
  }
44
55
 
45
56
  .app-nav {
@@ -51,6 +62,8 @@
51
62
  list-style: none;
52
63
  align-items: center;
53
64
  padding: 0;
65
+ width: max-content; /* Ensure it takes the width of all items */
66
+ min-width: 100%; /* Start at 100% width */
54
67
 
55
68
  .navDropdown {
56
69
  border-radius: var(--br);
@@ -64,6 +77,7 @@
64
77
  transition: all 0.15s cubic-bezier(0.25, 0.8, 0.25, 1);
65
78
  box-sizing: border-box;
66
79
  position: relative;
80
+ flex-shrink: 0; /* Prevent items from shrinking too much */
67
81
 
68
82
  a,
69
83
  span {
@@ -441,6 +455,15 @@
441
455
  .navwrap {
442
456
  width: 100%;
443
457
  position: relative;
458
+ overflow-y: auto; /* Enable vertical scrolling */
459
+ max-height: calc(100vh - 50px); /* Subtract the logo height */
460
+ scrollbar-width: none; /* Firefox */
461
+ -ms-overflow-style: none; /* IE and Edge */
462
+
463
+ /* Hide scrollbar for Chrome/Safari/Opera */
464
+ &::-webkit-scrollbar {
465
+ display: none;
466
+ }
444
467
 
445
468
  .app-nav {
446
469
  width: 100%;
@@ -591,6 +614,15 @@
591
614
  flex-wrap: nowrap;
592
615
  justify-content: space-between;
593
616
  align-items: center;
617
+ overflow-x: auto; /* Allow horizontal scrolling if needed */
618
+ -webkit-overflow-scrolling: touch;
619
+ scrollbar-width: none; /* Firefox */
620
+ -ms-overflow-style: none; /* IE and Edge */
621
+
622
+ /* Hide scrollbar for Chrome/Safari/Opera */
623
+ &::-webkit-scrollbar {
624
+ display: none;
625
+ }
594
626
  }
595
627
 
596
628
  .content-title {
@@ -644,6 +676,17 @@
644
676
  }
645
677
 
646
678
  @media (max-width: 1024px) {
679
+ .hwrap {
680
+ grid-template-columns: auto 1fr auto;
681
+ }
682
+
683
+ .navwrap {
684
+ position: relative;
685
+ overflow-x: auto;
686
+ -webkit-overflow-scrolling: touch;
687
+ padding: 0;
688
+ /* No fade indicators - they don't work well with scrolling */
689
+ }
647
690
  .app-container {
648
691
  width: 100%;
649
692
  min-height: 100vh;
@@ -914,6 +957,19 @@
914
957
  }
915
958
 
916
959
  @media (max-width: 960px) {
960
+ .hwrap {
961
+ grid-template-columns: auto 1fr auto;
962
+ gap: 0.5rem;
963
+ }
964
+
965
+ .navwrap {
966
+ margin: 0;
967
+ padding: 0;
968
+ }
969
+
970
+ .app-nav {
971
+ gap: 0.25rem; /* Reduce gap between items on smaller screens */
972
+ }
917
973
  .userdetails {
918
974
  display: none;
919
975
  visibility: hidden;
@@ -953,6 +1009,13 @@
953
1009
  flex-wrap: nowrap;
954
1010
  align-content: center;
955
1011
  width: 50%;
1012
+ min-width: 150px; /* Ensure minimum width on small screens */
1013
+ position: relative;
1014
+
1015
+ input {
1016
+ min-width: 0; /* Allow input to shrink below its content size */
1017
+ flex: 1; /* Take available space */
1018
+ }
956
1019
 
957
1020
  button {
958
1021
  width: max-content;
@@ -962,6 +1025,7 @@
962
1025
  background: var(--primary-color);
963
1026
  appearance: none;
964
1027
  border-radius: var(--br);
1028
+ flex-shrink: 0; /* Prevent button from shrinking */
965
1029
 
966
1030
  svg {
967
1031
  display: block;
@@ -970,4 +1034,14 @@
970
1034
  max-width: 20px;
971
1035
  }
972
1036
  }
1037
+
1038
+ @media (max-width: 768px) {
1039
+ width: auto; /* Allow to take natural width */
1040
+ flex: 1; /* Take available space */
1041
+
1042
+ button {
1043
+ margin: 0 0.25rem;
1044
+ padding: 0.15rem 0.35rem;
1045
+ }
1046
+ }
973
1047
  }
@@ -1,8 +1,15 @@
1
+ .tableFilterContainer {
2
+ width: 100%;
3
+ position: relative;
4
+ display: block;
5
+ }
6
+
1
7
  .tableFilter {
2
8
  list-style: none;
3
9
  padding: 0;
4
10
  margin: 0;
5
11
  font-size: 1rem;
12
+ width: 100%;
6
13
  }
7
14
 
8
15
  .collapsibleMenu {
@@ -51,3 +58,81 @@
51
58
  background: var(--item-color) !important;
52
59
  color: var(--paragraph-color) !important;
53
60
  }
61
+
62
+ /* Mobile menu toggle button */
63
+ .mobileToggle {
64
+ display: none;
65
+ align-items: center;
66
+ justify-content: space-between;
67
+ padding: 12px 15px;
68
+ background: var(--primary-color, #0b3b2e);
69
+ color: white;
70
+ border-radius: 4px;
71
+ cursor: pointer;
72
+ margin-bottom: 10px;
73
+ border: none;
74
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
75
+ }
76
+
77
+ .activeFilterLabel {
78
+ font-weight: 600;
79
+ color: white;
80
+ }
81
+
82
+ .mobileMenuIcon {
83
+ display: flex;
84
+ flex-direction: column;
85
+ justify-content: space-between;
86
+ width: 20px;
87
+ height: 14px;
88
+ position: relative;
89
+ transition: all 0.3s ease;
90
+ }
91
+
92
+ .mobileMenuIcon span {
93
+ display: block;
94
+ height: 2px;
95
+ width: 100%;
96
+ background-color: white;
97
+ border-radius: 2px;
98
+ transition: all 0.3s ease;
99
+ }
100
+
101
+ .mobileMenuIcon.open span:nth-child(1) {
102
+ transform: translateY(6px) rotate(45deg);
103
+ }
104
+
105
+ .mobileMenuIcon.open span:nth-child(2) {
106
+ opacity: 0;
107
+ }
108
+
109
+ .mobileMenuIcon.open span:nth-child(3) {
110
+ transform: translateY(-6px) rotate(-45deg);
111
+ }
112
+
113
+ /* Media queries for responsive design */
114
+ @media (max-width: 1024px) {
115
+ .mobileToggle {
116
+ display: flex;
117
+ }
118
+
119
+ .tableFilter {
120
+ display: none;
121
+ max-height: 0;
122
+ overflow: hidden;
123
+ transition: max-height 0.3s ease;
124
+ }
125
+
126
+ .tableFilter.mobileOpen {
127
+ display: block;
128
+ max-height: 1000px;
129
+ overflow-y: auto;
130
+ border: 1px solid rgba(0, 0, 0, 0.1);
131
+ border-radius: 4px;
132
+ margin-bottom: 10px;
133
+ }
134
+
135
+ .link {
136
+ padding: 12px 20px; /* Larger touch targets for mobile */
137
+ }
138
+ }