ml-ui-lib 1.0.28 → 1.0.30

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.
@@ -2,11 +2,18 @@
2
2
  Navbar Base
3
3
  ------------------------- */
4
4
  .navbar {
5
+ position: sticky;
5
6
  background: #fff;
6
7
  border-bottom: 1px solid #eee;
7
8
  width: 100%;
8
9
  z-index: 100;
9
10
  top: 0;
11
+ transition: transform 0.2s ease-in-out;
12
+ }
13
+
14
+ /* Hide navbar when scrolling down */
15
+ .navbar--hidden {
16
+ transform: translateY(-100%);
10
17
  }
11
18
 
12
19
  .navbar-container {
@@ -15,6 +22,7 @@
15
22
  align-items: center;
16
23
  max-width: 1200px;
17
24
  margin: 0 auto;
25
+ padding: 1rem;
18
26
  }
19
27
 
20
28
  .navbar-logo img {
@@ -26,7 +34,6 @@
26
34
  ------------------------- */
27
35
  .nav-items {
28
36
  display: flex;
29
- overflow: none;
30
37
  gap: 1.5rem;
31
38
  }
32
39
 
@@ -38,14 +45,15 @@
38
45
  cursor: pointer;
39
46
  }
40
47
 
41
- /* Dropdown menu */
48
+ /* Desktop dropdown bubble */
42
49
  .navbar-dropdown-content {
50
+ display: flex;
51
+ flex-direction: column;
43
52
  position: relative;
44
53
  top: 15px;
45
54
  padding: 12px 45px 12px 20px;
46
55
  background: #fff;
47
56
  border-radius: 22px;
48
- /* bubble feel */
49
57
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
50
58
  }
51
59
 
@@ -53,26 +61,23 @@
53
61
  content: "";
54
62
  position: absolute;
55
63
  top: -8px;
56
- right: 30px;
57
- /* adjust to align under text */
64
+ right: 37px;
58
65
  width: 15px;
59
66
  height: 15px;
60
67
  background: #fff;
61
68
  transform: rotate(45deg);
62
- box-shadow: -4px 2px 8px rgba(0, 0, 0, 0.05);
69
+ box-shadow: -2px -3px 4px #0000000d
63
70
  }
64
71
 
65
-
66
72
  .navbar-dropdown {
67
73
  position: absolute;
68
74
  top: 20px;
69
- /* slightly lower for bubble spacing */
70
75
  left: -35px;
71
76
  display: none;
72
77
  z-index: 10;
78
+ flex-direction: column;
73
79
  }
74
80
 
75
-
76
81
  .navbar-dropdown-link {
77
82
  padding: 10px 15px;
78
83
  color: #000;
@@ -84,22 +89,47 @@
84
89
  background: #f2f2f2;
85
90
  }
86
91
 
87
- /* Show dropdown on hover (desktop) */
88
- .navbar-item.has-dropdown:hover .navbar-dropdown {
92
+ /* Desktop hover */
93
+ /* INITIAL STATE */
94
+ .navbar-dropdown {
89
95
  display: flex;
96
+ /* keep it rendered */
97
+ flex-direction: column;
98
+ opacity: 0;
99
+ transform: translateY(-10px);
100
+ pointer-events: none;
101
+ transition:
102
+ opacity 0.25s ease,
103
+ transform 0.25s ease;
90
104
  }
91
105
 
92
- /* Add linear caret for items with dropdown */
106
+ /* HOVER OPEN */
107
+ .navbar-item.has-dropdown:hover .navbar-dropdown {
108
+ opacity: 1;
109
+ transform: translateY(0);
110
+ pointer-events: auto;
111
+ }
112
+
113
+ /* Caret arrow */
93
114
  .navbar-item.has-dropdown>.navbar-link::after {
94
115
  content: ">";
95
116
  display: inline-block;
96
117
  margin-left: 6px;
97
- font-size: 0.8em;
118
+ font-size: 1em;
98
119
  font-weight: bold;
99
120
  transition: transform 0.3s ease;
100
121
  transform: rotate(90deg);
101
122
  }
102
123
 
124
+ .navbar-item.has-dropdown:hover>.navbar-link::after {
125
+ transform: rotate(270deg);
126
+ color: #e00000;
127
+ }
128
+
129
+ .separator {
130
+ display: block !important;
131
+ }
132
+
103
133
  /* -------------------------
104
134
  Navbar Links
105
135
  ------------------------- */
@@ -115,28 +145,15 @@
115
145
  font-weight: 400;
116
146
  font-size: 17px;
117
147
  transition: color 0.2s;
118
- display: inline-block;
119
- /* allows width & height */
120
- width: auto;
121
- /* default for desktop */
122
148
  }
123
149
 
124
- /* Style for parent items with sublinks (desktop) */
125
150
  .navbar-link.parent-link {
126
151
  cursor: default;
127
- /* show pointer as normal arrow, not hand */
128
152
  color: #333 !important;
129
- /* normal text color */
130
- /* font-weight: 500; */
131
- /* optional: slightly bolder */
132
153
  }
133
154
 
134
- /* Prevent hover effect for parent items */
135
155
  .navbar-link.parent-link:hover {
136
- color: #333;
137
- /* do not turn red */
138
156
  background: none;
139
- /* no background change */
140
157
  }
141
158
 
142
159
  .navbar-links a:hover {
@@ -188,88 +205,72 @@
188
205
  /* -------------------------
189
206
  Responsive
190
207
  ------------------------- */
191
- @media (min-width: 1000px) {
192
- .navbar-container {
193
- padding: 1rem 5rem;
194
- }
195
- }
196
-
197
- @media (min-width: 993px) {
198
- .navbar-container {
199
- padding: 1rem 0;
200
- }
201
- }
202
-
203
- @media (max-width: 500px) {
204
- .navbar-container {
205
- margin: 0 !important;
206
- }
207
- }
208
-
209
- .mobile-login-btn {
210
- display: none;
211
- }
212
-
213
- /* -------------------------
214
- Mobile Menu
215
- ------------------------- */
216
208
  @media (max-width: 992px) {
217
- /* .navbar-item.has-dropdown>.navbar-link::after {
218
- display: none;
219
- } */
220
-
221
- .separator {
222
- display: none;
209
+ .navbar-container {
210
+ margin: 0 20px;
211
+ padding: 1rem 1.5rem;
212
+ justify-content: space-between;
223
213
  }
224
214
 
225
- .mobile-login-btn {
226
- display: block;
215
+ .burger {
216
+ display: flex;
217
+ z-index: 10000;
227
218
  }
228
219
 
220
+ .separator,
229
221
  .login-btn {
230
222
  display: none !important;
231
223
  }
232
224
 
233
- .burger {
234
- display: flex;
235
- z-index: 10000;
236
- }
237
-
238
- .navbar-container {
239
- margin: 0 20px;
240
- padding: 1rem 1.5rem;
241
- justify-content: space-between;
225
+ .mobile-login-btn {
226
+ display: block;
227
+ position: fixed;
228
+ bottom: 0;
229
+ /* left: 0; */
230
+ width: 400px;
231
+ padding: 1.3rem;
232
+ border: none;
233
+ color: #333;
234
+ font-size: 1.1rem;
235
+ font-weight: 600;
236
+ z-index: 99;
242
237
  }
243
238
 
244
- .nav-items {
245
- gap: 0px;
246
- flex-direction: column;
247
- overflow: auto;
239
+ .navbar-dropdown-content {
240
+ padding: 0px !important;
248
241
  }
249
242
 
250
243
  .navbar-links {
251
244
  position: fixed;
252
245
  top: 0;
253
246
  right: -100%;
254
- height: 100vh;
255
247
  width: 400px;
248
+ height: 100vh;
256
249
  background: #fff;
257
250
  flex-direction: column;
258
251
  padding: 65px 0px 70px;
259
252
  transition: right 0.3s ease-in-out;
260
253
  box-shadow: -4px 0 12px rgba(0, 0, 0, 0.1);
261
254
  z-index: 9999;
255
+ overflow-y: auto;
262
256
  }
263
257
 
264
258
  .navbar-links.open {
265
259
  right: 0;
266
260
  }
267
261
 
262
+ .nav-items {
263
+ flex-direction: column;
264
+ gap: 0;
265
+ overflow: visible;
266
+ }
267
+
268
268
  .navbar-links a {
269
269
  display: block;
270
270
  width: 100%;
271
271
  padding: 1rem 50px;
272
272
  font-size: 1.2rem;
273
+ color: #333;
273
274
  }
274
275
 
275
276
  .navbar-links a:hover {
@@ -277,48 +278,79 @@
277
278
  color: #fff;
278
279
  }
279
280
 
280
- .navbar-links a.active:hover {
281
- color: #fff;
281
+ /* -------------------------
282
+ Mobile accordion
283
+ ------------------------- */
284
+ .navbar-item.has-dropdown {
285
+ display: flex;
286
+ flex-direction: column;
287
+ width: 100%;
288
+ }
289
+
290
+ /* arrow */
291
+ .navbar-item.has-dropdown .navbar-link::after {
292
+ content: "▸";
293
+ margin-left: auto;
294
+ transition: transform 0.3s ease;
295
+ float: right;
282
296
  }
283
297
 
284
- /* Mobile dropdown inside sliding menu */
298
+ .navbar-item.has-dropdown.open .navbar-link::after {
299
+ transform: rotate(270deg);
300
+ color: #e00000;
301
+ }
302
+
303
+ /* remove absolute, make dropdown push content */
285
304
  .navbar-dropdown {
286
- position: static;
287
- display: flex !important;
305
+ display: none;
288
306
  flex-direction: column;
289
- background: transparent;
307
+ width: 100%;
308
+ /* margin-top: 0.3rem; */
309
+ }
310
+
311
+ /* show when open */
312
+ .navbar-item.has-dropdown.open .navbar-dropdown {
313
+ position: static;
314
+ display: contents;
315
+ }
316
+
317
+ .navbar-item.has-dropdown.open .navbar-dropdown a {
318
+ padding-left: 70px;
319
+ }
320
+
321
+ .navbar-dropdown-content {
322
+ padding: 0.5rem 1rem;
323
+ /* margin: 0.2rem 0; */
324
+ top: 0px;
325
+ border-radius: 8px;
326
+ background: #fff;
290
327
  box-shadow: none;
291
- padding: 0px;
328
+ }
329
+
330
+ .navbar-dropdown-content::before,
331
+ .navbar-dropdown-content::after {
332
+ display: none;
292
333
  }
293
334
 
294
335
  .navbar-dropdown-link {
295
- padding-left: 50px;
336
+ padding: 0.7rem 1rem;
337
+ font-size: 1rem;
296
338
  color: #333;
339
+ white-space: normal;
297
340
  }
298
341
 
299
342
  .has-dropdown .navbar-dropdown-link {
300
- padding: 1rem 70px;
301
- }
302
-
303
- /* MOBILE LOGIN FIXED AT BOTTOM */
304
- .mobile-login-btn {
305
- position: absolute;
306
- bottom: 0;
307
- left: 0;
308
- width: 100%;
309
- padding: 1.3rem;
310
- border: none;
311
- color: #333;
312
- font-size: 1.1rem;
313
- font-weight: 600;
343
+ padding-left: 1.5rem;
314
344
  }
315
345
  }
316
346
 
347
+
317
348
  /* -------------------------
318
- Login Button
349
+ Login & Panels
319
350
  ------------------------- */
320
351
  .login-btn {
321
- color: #333;
352
+ color: #e00000;
353
+ font-weight: 700;
322
354
  border: none;
323
355
  border-radius: 6px;
324
356
  cursor: pointer;
@@ -334,11 +366,6 @@
334
366
  font-weight: 500;
335
367
  }
336
368
 
337
- .navbar-login-panel .spb-header {
338
- padding: 0 !important;
339
- border-bottom: none !important;
340
- }
341
-
342
369
  .navbar-panel-content {
343
370
  padding: 20px;
344
371
  }
@@ -373,7 +400,6 @@
373
400
  width: 280px;
374
401
  height: 80px;
375
402
  background: url(https://mlhuillier.com/img/revamp/ml-logo-2.svg) center/contain no-repeat;
376
- opacity: 1;
377
403
  pointer-events: none;
378
404
  z-index: 1;
379
405
  }
@@ -386,6 +412,10 @@
386
412
  .navbar-links {
387
413
  width: 100%;
388
414
  }
415
+
416
+ .mobile-login-btn {
417
+ width: 100%;
418
+ }
389
419
  }
390
420
 
391
421
  /* Right-side group */
@@ -3,13 +3,15 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useState, useEffect } from 'react';
4
4
  import './Navbar.css';
5
5
  import { SlidingPanel } from '../SlidingPanel/SlidingPanel';
6
- export const Navbar = ({ siteUrl, items, logoSrc = '', logoAlt = 'M Lhuillier Logo', logoWidth = 220, logoHeight = 40, className = '', login = false, logedinData = null, loginContent, otherContent, onClose, }) => {
6
+ export const Navbar = ({ siteUrl, items, logoSrc = '', logoAlt = 'Logo', logoWidth = 220, logoHeight = 40, className = '', login = false, logedinData = null, loginContent, otherContent, onClose, }) => {
7
7
  const [menuOpen, setMenuOpen] = useState(false);
8
8
  const [showLogin, setShowLogin] = useState(false);
9
9
  const [isClosing, setIsClosing] = useState(false);
10
10
  const [isMobile, setIsMobile] = useState(false);
11
11
  const [currentPath, setCurrentPath] = useState(null);
12
12
  const [openDropdown, setOpenDropdown] = useState(null);
13
+ const [hideNavbar, setHideNavbar] = useState(false);
14
+ const [lastScrollY, setLastScrollY] = useState(0);
13
15
  useEffect(() => {
14
16
  const handleResize = () => setIsMobile(window.innerWidth <= 992);
15
17
  handleResize();
@@ -21,13 +23,10 @@ export const Navbar = ({ siteUrl, items, logoSrc = '', logoAlt = 'M Lhuillier Lo
21
23
  if (!menuOpen)
22
24
  setOpenDropdown(null);
23
25
  }, [menuOpen]);
24
- const toggleMenu = () => setMenuOpen((prev) => !prev);
26
+ const toggleMenu = () => setMenuOpen(prev => !prev);
25
27
  const toggleLogin = () => {
26
28
  setMenuOpen(false);
27
- setShowLogin((prev) => !prev);
28
- };
29
- const toggleDropdown = (name) => {
30
- setOpenDropdown(prev => (prev === name ? null : name));
29
+ setShowLogin(prev => !prev);
31
30
  };
32
31
  const handleInternalClose = () => {
33
32
  setIsClosing(true);
@@ -39,45 +38,63 @@ export const Navbar = ({ siteUrl, items, logoSrc = '', logoAlt = 'M Lhuillier Lo
39
38
  }, 350);
40
39
  };
41
40
  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
- }
41
+ document.body.style.overflow = menuOpen ? 'hidden' : '';
42
+ document.body.style.touchAction = menuOpen ? 'none' : '';
50
43
  return () => {
51
44
  document.body.style.overflow = '';
52
45
  document.body.style.touchAction = '';
53
46
  };
54
47
  }, [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 => {
48
+ useEffect(() => {
49
+ let ticking = false;
50
+ const REVEAL_THRESHOLD = 10; // adjust (px). 60–120
51
+ const handleScroll = () => {
52
+ if (menuOpen)
53
+ return;
54
+ const currentY = window.scrollY;
55
+ if (!ticking) {
56
+ window.requestAnimationFrame(() => {
57
+ if (currentY <= 0) {
58
+ setHideNavbar(false);
59
+ setLastScrollY(0);
60
+ ticking = false;
61
+ return;
62
+ }
63
+ const diff = lastScrollY - currentY;
64
+ // scrolling DOWN → hide immediately
65
+ if (currentY > lastScrollY) {
66
+ setHideNavbar(true);
67
+ }
68
+ // scrolling UP → only show after threshold
69
+ else if (diff > REVEAL_THRESHOLD) {
70
+ setHideNavbar(false);
71
+ }
72
+ setLastScrollY(currentY);
73
+ ticking = false;
74
+ });
75
+ ticking = true;
76
+ }
77
+ };
78
+ window.addEventListener('scroll', handleScroll, { passive: true });
79
+ return () => window.removeEventListener('scroll', handleScroll);
80
+ }, [lastScrollY, menuOpen]);
81
+ return (_jsxs(_Fragment, { children: [_jsx("nav", { className: `navbar ${hideNavbar ? 'navbar--hidden' : ''} ${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
82
  const hasSubLinks = item.subLinks && item.subLinks.length > 0;
57
- const linkPath = new URL(item.link, 'https://dummybase').pathname;
83
+ const linkPath = new URL(item.link || '#', 'https://dummybase').pathname;
58
84
  const isActive = currentPath === linkPath;
59
85
  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
- }
86
+ return (_jsxs("div", { className: `navbar-item ${hasSubLinks ? 'has-dropdown' : ''} ${isDropdownOpen ? 'open' : ''}`, children: [_jsx("a", { href: hasSubLinks ? '#' : item.link, target: item.target || '_self', className: `navbar-link ${isActive ? 'active' : ''} ${hasSubLinks ? 'parent-link' : ''}`, onClick: (e) => {
87
+ if (hasSubLinks && isMobile) {
88
+ e.preventDefault();
89
+ setOpenDropdown(prev => (prev === item.name ? null : item.name));
69
90
  }
70
91
  else {
71
92
  setMenuOpen(false);
72
93
  }
73
- }, children: item.name }), hasSubLinks && (_jsx("div", { className: "navbar-dropdown", style: {
74
- display: isMobile
75
- ? isDropdownOpen ? 'flex' : 'none'
76
- : undefined,
77
- }, children: _jsx("div", { className: 'navbar-dropdown-content', children: item.subLinks.map(sub => (_jsx("a", { href: sub.link, target: sub.target || '_self', className: "navbar-dropdown-link", onClick: () => {
94
+ }, children: item.name }), hasSubLinks && (_jsx("div", { className: "navbar-dropdown", children: _jsx("div", { className: "navbar-dropdown-content", children: item.subLinks.map(sub => (_jsx("a", { href: sub.link, target: sub.target || '_self', className: "navbar-dropdown-link", onClick: () => {
78
95
  setMenuOpen(false);
79
96
  setOpenDropdown(null);
80
97
  }, 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 })] }) })) }))] }));
98
+ }) }), isMobile && login && !logedinData && (_jsx("div", { className: 'navbar-item', style: { background: '#ffffff' }, children: _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, 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("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 })] }) })) }))] }));
82
99
  };
83
100
  export default Navbar;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ml-ui-lib",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.esm.js",
6
6
  "types": "dist/index.d.ts",