ml-ui-lib 1.0.25 → 1.0.27

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.
@@ -1,3 +1,6 @@
1
+ /* -------------------------
2
+ Navbar Base
3
+ ------------------------- */
1
4
  .navbar {
2
5
  background: #fff;
3
6
  border-bottom: 1px solid #eee;
@@ -18,17 +21,70 @@
18
21
  max-height: 50px;
19
22
  }
20
23
 
21
-
24
+ /* -------------------------
25
+ Nav Items
26
+ ------------------------- */
22
27
  .nav-items {
23
28
  display: flex;
24
- overflow: auto;
29
+ overflow: none;
25
30
  gap: 1.5rem;
26
31
  }
27
32
 
33
+ .navbar-item {
34
+ position: relative;
35
+ }
36
+
37
+ .navbar-item.has-dropdown>.navbar-link {
38
+ cursor: pointer;
39
+ }
40
+
41
+ /* Dropdown menu */
42
+ .navbar-dropdown {
43
+ /* hidden by default */
44
+ display: none;
45
+ padding: 20px 25px 10px 10px;
46
+ position: absolute;
47
+ top: 20px;
48
+ left: -25px;
49
+ background: #fff;
50
+ min-width: 180px;
51
+ /* box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15); */
52
+ flex-direction: column;
53
+ z-index: 999;
54
+ }
55
+
56
+ .navbar-dropdown-link {
57
+ padding: 10px 15px;
58
+ color: #000;
59
+ text-decoration: none;
60
+ white-space: nowrap;
61
+ }
62
+
63
+ .navbar-dropdown-link:hover {
64
+ background: #f2f2f2;
65
+ }
66
+
67
+ /* Show dropdown on hover (desktop) */
68
+ .navbar-item.has-dropdown:hover .navbar-dropdown {
69
+ display: flex;
70
+ }
71
+
72
+ /* Add linear caret for items with dropdown */
73
+ .navbar-item.has-dropdown>.navbar-link::after {
74
+ content: ">";
75
+ display: inline-block;
76
+ margin-left: 6px;
77
+ font-size: 0.8em;
78
+ font-weight: bold;
79
+ transition: transform 0.3s ease;
80
+ transform: rotate(90deg);
81
+ }
28
82
 
83
+ /* -------------------------
84
+ Navbar Links
85
+ ------------------------- */
29
86
  .navbar-links {
30
87
  display: flex;
31
- /* gap: 1.5rem; */
32
88
  transition: all 0.3s ease;
33
89
  margin-left: 35px;
34
90
  }
@@ -39,6 +95,28 @@
39
95
  font-weight: 400;
40
96
  font-size: 17px;
41
97
  transition: color 0.2s;
98
+ display: inline-block;
99
+ /* allows width & height */
100
+ width: auto;
101
+ /* default for desktop */
102
+ }
103
+
104
+ /* Style for parent items with sublinks (desktop) */
105
+ .navbar-link.parent-link {
106
+ cursor: default;
107
+ /* show pointer as normal arrow, not hand */
108
+ color: #333 !important;
109
+ /* normal text color */
110
+ /* font-weight: 500; */
111
+ /* optional: slightly bolder */
112
+ }
113
+
114
+ /* Prevent hover effect for parent items */
115
+ .navbar-link.parent-link:hover {
116
+ color: #333;
117
+ /* do not turn red */
118
+ background: none;
119
+ /* no background change */
42
120
  }
43
121
 
44
122
  .navbar-links a:hover {
@@ -51,12 +129,9 @@
51
129
  color: #ff0000;
52
130
  }
53
131
 
54
- /* .navbar-links a.active:hover {
55
- color: #fff;
56
- } */
57
-
58
-
59
- /* Burger Icon */
132
+ /* -------------------------
133
+ Burger Icon
134
+ ------------------------- */
60
135
  .burger {
61
136
  display: none;
62
137
  flex-direction: column;
@@ -90,7 +165,9 @@
90
165
  transform: translateY(-8px) rotate(-45deg);
91
166
  }
92
167
 
93
-
168
+ /* -------------------------
169
+ Responsive
170
+ ------------------------- */
94
171
  @media (min-width: 1000px) {
95
172
  .navbar-container {
96
173
  padding: 1rem 5rem;
@@ -101,22 +178,30 @@
101
178
  .navbar-container {
102
179
  padding: 1rem 0;
103
180
  }
104
-
105
181
  }
106
182
 
107
183
  @media (max-width: 500px) {
108
184
  .navbar-container {
109
185
  margin: 0 !important;
110
186
  }
111
-
112
187
  }
113
188
 
114
189
  .mobile-login-btn {
115
190
  display: none;
116
191
  }
117
192
 
118
- /* Responsive Menu */
193
+ /* -------------------------
194
+ Mobile Menu
195
+ ------------------------- */
119
196
  @media (max-width: 992px) {
197
+ /* .navbar-item.has-dropdown>.navbar-link::after {
198
+ display: none;
199
+ } */
200
+
201
+ .separator {
202
+ display: none;
203
+ }
204
+
120
205
  .mobile-login-btn {
121
206
  display: block;
122
207
  }
@@ -136,11 +221,10 @@
136
221
  justify-content: space-between;
137
222
  }
138
223
 
139
- /* ⭐ SLIDE-IN MENU */
140
-
141
224
  .nav-items {
142
225
  gap: 0px;
143
226
  flex-direction: column;
227
+ overflow: auto;
144
228
  }
145
229
 
146
230
  .navbar-links {
@@ -162,8 +246,9 @@
162
246
  }
163
247
 
164
248
  .navbar-links a {
165
- padding: 1rem 50px;
249
+ display: block;
166
250
  width: 100%;
251
+ padding: 1rem 50px;
167
252
  font-size: 1.2rem;
168
253
  }
169
254
 
@@ -176,8 +261,26 @@
176
261
  color: #fff;
177
262
  }
178
263
 
264
+ /* Mobile dropdown inside sliding menu */
265
+ .navbar-dropdown {
266
+ position: static;
267
+ display: flex !important;
268
+ flex-direction: column;
269
+ background: transparent;
270
+ box-shadow: none;
271
+ padding: 0px;
272
+ }
179
273
 
180
- /* ⭐ MOBILE LOGIN FIXED AT BOTTOM */
274
+ .navbar-dropdown-link {
275
+ padding-left: 50px;
276
+ color: #333;
277
+ }
278
+
279
+ .has-dropdown .navbar-dropdown-link {
280
+ padding: 1rem 70px;
281
+ }
282
+
283
+ /* MOBILE LOGIN FIXED AT BOTTOM */
181
284
  .mobile-login-btn {
182
285
  position: absolute;
183
286
  bottom: 0;
@@ -185,20 +288,18 @@
185
288
  width: 100%;
186
289
  padding: 1.3rem;
187
290
  border: none;
188
- /* background: #d00000; */
189
291
  color: #333;
190
292
  font-size: 1.1rem;
191
293
  font-weight: 600;
192
- /* text-align: center; */
193
294
  }
194
295
  }
195
296
 
196
-
197
- /* Login Button */
297
+ /* -------------------------
298
+ Login Button
299
+ ------------------------- */
198
300
  .login-btn {
199
301
  color: #333;
200
302
  border: none;
201
- /* padding: 0.5rem 1.25rem; */
202
303
  border-radius: 6px;
203
304
  cursor: pointer;
204
305
  transition: background 0.3s ease;
@@ -213,7 +314,6 @@
213
314
  font-weight: 500;
214
315
  }
215
316
 
216
-
217
317
  .navbar-login-panel .spb-header {
218
318
  padding: 0 !important;
219
319
  border-bottom: none !important;
@@ -223,6 +323,7 @@
223
323
  padding: 20px;
224
324
  }
225
325
 
326
+ /* Overlay for desktop login panel */
226
327
  @media (min-width: 591px) {
227
328
  .overlay-side-content {
228
329
  position: fixed;
@@ -274,23 +375,21 @@
274
375
  gap: 20px;
275
376
  }
276
377
 
378
+ /* Scrollbar styling */
277
379
  ::-webkit-scrollbar {
278
380
  width: 5px;
279
381
  }
280
382
 
281
- /* Track */
282
383
  ::-webkit-scrollbar-track {
283
384
  box-shadow: inset 0 0 5px grey;
284
385
  border-radius: 10px;
285
386
  }
286
387
 
287
- /* Handle */
288
388
  ::-webkit-scrollbar-thumb {
289
389
  background: #757575;
290
390
  border-radius: 10px;
291
391
  }
292
392
 
293
- /* Handle on hover */
294
393
  ::-webkit-scrollbar-thumb:hover {
295
394
  background: #4f4f4f;
296
395
  }
@@ -1,9 +1,15 @@
1
1
  import React from 'react';
2
2
  import './Navbar.css';
3
+ export interface NavbarSubItem {
4
+ name: string;
5
+ link: string;
6
+ target?: '_blank' | '_self';
7
+ }
3
8
  export interface NavbarItem {
4
9
  name: string;
5
10
  link: string;
6
11
  target?: '_blank' | '_self';
12
+ subLinks?: NavbarSubItem[];
7
13
  }
8
14
  export interface NavbarProps {
9
15
  siteUrl?: string;
@@ -9,18 +9,26 @@ export const Navbar = ({ siteUrl, items, logoSrc = '', logoAlt = 'M Lhuillier Lo
9
9
  const [isClosing, setIsClosing] = useState(false);
10
10
  const [isMobile, setIsMobile] = useState(false);
11
11
  const [currentPath, setCurrentPath] = useState(null);
12
+ const [openDropdown, setOpenDropdown] = useState(null);
12
13
  useEffect(() => {
13
- const handleResize = () => setIsMobile(window.innerWidth <= 590);
14
+ const handleResize = () => setIsMobile(window.innerWidth <= 992);
14
15
  handleResize();
15
16
  setCurrentPath(window.location.pathname);
16
17
  window.addEventListener('resize', handleResize);
17
18
  return () => window.removeEventListener('resize', handleResize);
18
19
  }, []);
20
+ useEffect(() => {
21
+ if (!menuOpen)
22
+ setOpenDropdown(null);
23
+ }, [menuOpen]);
19
24
  const toggleMenu = () => setMenuOpen((prev) => !prev);
20
25
  const toggleLogin = () => {
21
26
  setMenuOpen(false);
22
27
  setShowLogin((prev) => !prev);
23
28
  };
29
+ const toggleDropdown = (name) => {
30
+ setOpenDropdown(prev => (prev === name ? null : name));
31
+ };
24
32
  const handleInternalClose = () => {
25
33
  setIsClosing(true);
26
34
  setTimeout(() => {
@@ -28,17 +36,48 @@ export const Navbar = ({ siteUrl, items, logoSrc = '', logoAlt = 'M Lhuillier Lo
28
36
  setIsClosing(false);
29
37
  document.body.style.overflow = '';
30
38
  onClose?.();
31
- }, 350); // match CSS transition
39
+ }, 350);
32
40
  };
33
- return (_jsxs(_Fragment, { children: [_jsx("nav", { className: `navbar ${className}`, children: _jsxs("div", { className: "navbar-container", children: [_jsxs("div", { style: { display: 'flex', flexDirection: 'row', gap: '10px', alignItems: 'center' }, children: [_jsx("div", { className: "navbar-logo", children: _jsx("a", { href: siteUrl, className: "navbar-brand", children: _jsx("img", { src: logoSrc, alt: logoAlt, width: logoWidth, height: logoHeight, style: { cursor: 'pointer', display: 'block' } }) }) }), _jsxs("div", { className: `navbar-links ${menuOpen ? 'open' : ''}`, children: [_jsx("div", { className: 'nav-items', children: items.map((item) => {
41
+ useEffect(() => {
42
+ if (menuOpen) {
43
+ document.body.style.overflow = 'hidden';
44
+ document.body.style.touchAction = 'none';
45
+ }
46
+ else {
47
+ document.body.style.overflow = '';
48
+ document.body.style.touchAction = '';
49
+ }
50
+ return () => {
51
+ document.body.style.overflow = '';
52
+ document.body.style.touchAction = '';
53
+ };
54
+ }, [menuOpen]);
55
+ return (_jsxs(_Fragment, { children: [_jsx("nav", { className: `navbar ${className}`, children: _jsxs("div", { className: "navbar-container", children: [_jsx("div", { className: "navbar-logo", children: _jsx("a", { href: siteUrl, className: "navbar-brand", children: _jsx("img", { src: logoSrc, alt: logoAlt, width: logoWidth, height: logoHeight, style: { cursor: 'pointer', display: 'block' } }) }) }), _jsxs("div", { style: { display: 'flex', flexDirection: 'row', gap: '10px', alignItems: 'center' }, children: [_jsxs("div", { className: `navbar-links ${menuOpen ? 'open' : ''}`, children: [_jsx("div", { className: "nav-items", children: items.map(item => {
56
+ const hasSubLinks = item.subLinks && item.subLinks.length > 0;
34
57
  const linkPath = new URL(item.link, 'https://dummybase').pathname;
35
58
  const isActive = currentPath === linkPath;
36
- return (_jsx("a", { href: item.link, target: item.target || '_self', className: `navbar-link ${isActive ? 'active' : ''}`, onClick: () => setMenuOpen(false), children: item.name.includes('-')
37
- ? item.name
38
- .split('-')
39
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
40
- .join(' & ')
41
- : item.name }, item.name));
42
- }) }), _jsx("button", { className: "mobile-login-btn", onClick: toggleLogin, children: "Login" })] })] }), _jsxs("div", { className: "navbar-right", children: [_jsxs("button", { className: `burger ${menuOpen ? 'active' : ''}`, onClick: toggleMenu, "aria-label": "Toggle menu", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }), !isMobile && login && !logedinData && (_jsx("span", { className: "login-btn", onClick: toggleLogin, children: "Login" }))] })] }) }), login && !logedinData && (_jsx(_Fragment, { children: !isMobile ? (showLogin && (_jsxs(_Fragment, { children: [_jsx("div", { className: "overlay-side-content", children: otherContent }), _jsx("div", { className: "navbar-login-panel", children: _jsxs(SlidingPanel, { isOpen: showLogin && !isClosing, width: "400px", height: "100%", position: "right", closeOnOverlayClick: false, onClose: handleInternalClose, children: [_jsx("div", { className: "navbar-panel-header" }), _jsx("div", { className: "navbar-panel-content", children: loginContent || _jsx("p", { children: "Login form goes here" }) })] }) })] }))) : (_jsx("div", { className: "navbar-login-panel", children: _jsxs(SlidingPanel, { isOpen: showLogin, height: "70%", position: "bottom", onClose: handleInternalClose, children: [_jsx("div", { className: "navbar-panel-header" }), _jsx("div", { className: "navbar-panel-content", children: loginContent || _jsx("p", { children: "Login form goes here" }) })] }) })) }))] }));
59
+ const isDropdownOpen = openDropdown === item.name;
60
+ return (_jsxs("div", { className: `navbar-item ${hasSubLinks ? 'has-dropdown' : ''} ${isDropdownOpen ? 'open' : ''}`, children: [_jsx("a", { href: hasSubLinks && !isMobile ? '#' : item.link, target: item.target || '_self', className: `navbar-link ${isActive ? 'active' : ''} ${hasSubLinks && !isMobile ? 'parent-link' : ''}`, onClick: (e) => {
61
+ if (hasSubLinks) {
62
+ if (isMobile) {
63
+ e.preventDefault();
64
+ toggleDropdown(item.name);
65
+ }
66
+ else {
67
+ e.preventDefault(); // prevent desktop click
68
+ }
69
+ }
70
+ else {
71
+ setMenuOpen(false);
72
+ }
73
+ }, children: item.name }), hasSubLinks && (_jsx("div", { className: "navbar-dropdown", style: {
74
+ display: isMobile
75
+ ? isDropdownOpen ? 'flex' : 'none'
76
+ : undefined,
77
+ }, children: item.subLinks.map(sub => (_jsx("a", { href: sub.link, target: sub.target || '_self', className: "navbar-dropdown-link", onClick: () => {
78
+ setMenuOpen(false);
79
+ setOpenDropdown(null);
80
+ }, children: sub.name }, sub.name))) }))] }, item.name));
81
+ }) }), isMobile && login && !logedinData && (_jsx("button", { className: "mobile-login-btn", onClick: toggleLogin, children: "Login" }))] }), _jsx("div", { className: 'separator', style: { color: '#aaaaaa', fontSize: '27px', padding: '0 15px' }, children: " | " }), _jsxs("div", { className: "navbar-right", children: [_jsxs("button", { className: `burger ${menuOpen ? 'active' : ''}`, onClick: toggleMenu, "aria-label": "Toggle menu", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }), !isMobile && login && !logedinData && (_jsx("span", { style: { color: '#e00000', fontWeight: '700' }, className: "login-btn", onClick: toggleLogin, children: "Login" }))] })] })] }) }), login && !logedinData && (_jsx(_Fragment, { children: !isMobile ? (showLogin && (_jsxs(_Fragment, { children: [_jsx("div", { className: "overlay-side-content", children: otherContent }), _jsx("div", { className: "navbar-login-panel", children: _jsxs(SlidingPanel, { isOpen: showLogin && !isClosing, width: "400px", height: "100%", position: "right", closeOnOverlayClick: false, onClose: handleInternalClose, children: [_jsx("div", { className: "navbar-panel-header" }), _jsx("div", { className: "navbar-panel-content", children: loginContent })] }) })] }))) : (_jsx("div", { className: "navbar-login-panel", children: _jsxs(SlidingPanel, { isOpen: showLogin, height: "70%", position: "bottom", onClose: handleInternalClose, children: [_jsx("div", { className: "navbar-panel-header" }), _jsx("div", { className: "navbar-panel-content", children: loginContent })] }) })) }))] }));
43
82
  };
44
83
  export default Navbar;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ml-ui-lib",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.esm.js",
6
6
  "types": "dist/index.d.ts",