ml-ui-lib 1.0.25 → 1.0.26

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,8 +221,6 @@
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;
@@ -162,8 +245,9 @@
162
245
  }
163
246
 
164
247
  .navbar-links a {
165
- padding: 1rem 50px;
248
+ display: block;
166
249
  width: 100%;
250
+ padding: 1rem 50px;
167
251
  font-size: 1.2rem;
168
252
  }
169
253
 
@@ -176,8 +260,26 @@
176
260
  color: #fff;
177
261
  }
178
262
 
263
+ /* Mobile dropdown inside sliding menu */
264
+ .navbar-dropdown {
265
+ position: static;
266
+ display: flex !important;
267
+ flex-direction: column;
268
+ background: transparent;
269
+ box-shadow: none;
270
+ padding: 0px;
271
+ }
179
272
 
180
- /* ⭐ MOBILE LOGIN FIXED AT BOTTOM */
273
+ .navbar-dropdown-link {
274
+ padding-left: 50px;
275
+ color: #333;
276
+ }
277
+
278
+ .has-dropdown .navbar-dropdown-link {
279
+ padding: 1rem 70px;
280
+ }
281
+
282
+ /* MOBILE LOGIN FIXED AT BOTTOM */
181
283
  .mobile-login-btn {
182
284
  position: absolute;
183
285
  bottom: 0;
@@ -185,20 +287,18 @@
185
287
  width: 100%;
186
288
  padding: 1.3rem;
187
289
  border: none;
188
- /* background: #d00000; */
189
290
  color: #333;
190
291
  font-size: 1.1rem;
191
292
  font-weight: 600;
192
- /* text-align: center; */
193
293
  }
194
294
  }
195
295
 
196
-
197
- /* Login Button */
296
+ /* -------------------------
297
+ Login Button
298
+ ------------------------- */
198
299
  .login-btn {
199
300
  color: #333;
200
301
  border: none;
201
- /* padding: 0.5rem 1.25rem; */
202
302
  border-radius: 6px;
203
303
  cursor: pointer;
204
304
  transition: background 0.3s ease;
@@ -213,7 +313,6 @@
213
313
  font-weight: 500;
214
314
  }
215
315
 
216
-
217
316
  .navbar-login-panel .spb-header {
218
317
  padding: 0 !important;
219
318
  border-bottom: none !important;
@@ -223,6 +322,7 @@
223
322
  padding: 20px;
224
323
  }
225
324
 
325
+ /* Overlay for desktop login panel */
226
326
  @media (min-width: 591px) {
227
327
  .overlay-side-content {
228
328
  position: fixed;
@@ -274,23 +374,21 @@
274
374
  gap: 20px;
275
375
  }
276
376
 
377
+ /* Scrollbar styling */
277
378
  ::-webkit-scrollbar {
278
379
  width: 5px;
279
380
  }
280
381
 
281
- /* Track */
282
382
  ::-webkit-scrollbar-track {
283
383
  box-shadow: inset 0 0 5px grey;
284
384
  border-radius: 10px;
285
385
  }
286
386
 
287
- /* Handle */
288
387
  ::-webkit-scrollbar-thumb {
289
388
  background: #757575;
290
389
  border-radius: 10px;
291
390
  }
292
391
 
293
- /* Handle on hover */
294
392
  ::-webkit-scrollbar-thumb:hover {
295
393
  background: #4f4f4f;
296
394
  }
@@ -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,6 +9,7 @@ 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
14
  const handleResize = () => setIsMobile(window.innerWidth <= 590);
14
15
  handleResize();
@@ -16,11 +17,18 @@ export const Navbar = ({ siteUrl, items, logoSrc = '', logoAlt = 'M Lhuillier Lo
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.26",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.esm.js",
6
6
  "types": "dist/index.d.ts",