ml-ui-lib 1.0.24 → 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.
@@ -2,8 +2,12 @@ import React from "react";
2
2
  import "./Button.css";
3
3
  export interface ButtonProps {
4
4
  label: string;
5
- variant?: "default" | "white" | "red" | "black" | "tranparent-white";
5
+ variant?: "default" | "white" | "red" | "black" | "transparent-white";
6
6
  onClick?: () => void;
7
7
  disabled?: boolean;
8
+ /** Optional custom colors */
9
+ bgColor?: string;
10
+ textColor?: string;
11
+ borderColor?: string;
8
12
  }
9
13
  export declare const Button: React.FC<ButtonProps>;
@@ -1,5 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import "./Button.css";
3
- export const Button = ({ label, variant = "default", onClick, disabled = false }) => {
4
- return (_jsx("button", { className: `btn btn-${variant}`, onClick: onClick, disabled: disabled, children: label }));
3
+ export const Button = ({ label, variant = "default", onClick, disabled = false, bgColor, textColor, borderColor }) => {
4
+ const customStyle = {
5
+ background: bgColor,
6
+ color: textColor,
7
+ border: borderColor ? `1px solid ${borderColor}` : undefined
8
+ };
9
+ return (_jsx("button", { className: `btn btn-${variant}`, onClick: onClick, disabled: disabled, style: customStyle, children: label }));
5
10
  };
@@ -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
 
@@ -172,7 +256,30 @@
172
256
  color: #fff;
173
257
  }
174
258
 
175
- /* MOBILE LOGIN FIXED AT BOTTOM */
259
+ .navbar-links a.active:hover {
260
+ color: #fff;
261
+ }
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
+ }
272
+
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 */
176
283
  .mobile-login-btn {
177
284
  position: absolute;
178
285
  bottom: 0;
@@ -180,20 +287,18 @@
180
287
  width: 100%;
181
288
  padding: 1.3rem;
182
289
  border: none;
183
- /* background: #d00000; */
184
290
  color: #333;
185
291
  font-size: 1.1rem;
186
292
  font-weight: 600;
187
- /* text-align: center; */
188
293
  }
189
294
  }
190
295
 
191
-
192
- /* Login Button */
296
+ /* -------------------------
297
+ Login Button
298
+ ------------------------- */
193
299
  .login-btn {
194
300
  color: #333;
195
301
  border: none;
196
- /* padding: 0.5rem 1.25rem; */
197
302
  border-radius: 6px;
198
303
  cursor: pointer;
199
304
  transition: background 0.3s ease;
@@ -208,7 +313,6 @@
208
313
  font-weight: 500;
209
314
  }
210
315
 
211
-
212
316
  .navbar-login-panel .spb-header {
213
317
  padding: 0 !important;
214
318
  border-bottom: none !important;
@@ -218,6 +322,7 @@
218
322
  padding: 20px;
219
323
  }
220
324
 
325
+ /* Overlay for desktop login panel */
221
326
  @media (min-width: 591px) {
222
327
  .overlay-side-content {
223
328
  position: fixed;
@@ -269,23 +374,21 @@
269
374
  gap: 20px;
270
375
  }
271
376
 
377
+ /* Scrollbar styling */
272
378
  ::-webkit-scrollbar {
273
379
  width: 5px;
274
380
  }
275
381
 
276
- /* Track */
277
382
  ::-webkit-scrollbar-track {
278
383
  box-shadow: inset 0 0 5px grey;
279
384
  border-radius: 10px;
280
385
  }
281
386
 
282
- /* Handle */
283
387
  ::-webkit-scrollbar-thumb {
284
388
  background: #757575;
285
389
  border-radius: 10px;
286
390
  }
287
391
 
288
- /* Handle on hover */
289
392
  ::-webkit-scrollbar-thumb:hover {
290
393
  background: #4f4f4f;
291
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;
@@ -6,8 +6,10 @@ import { SlidingPanel } from '../SlidingPanel/SlidingPanel';
6
6
  export const Navbar = ({ siteUrl, items, logoSrc = '', logoAlt = 'M Lhuillier 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
+ const [isClosing, setIsClosing] = useState(false);
9
10
  const [isMobile, setIsMobile] = useState(false);
10
11
  const [currentPath, setCurrentPath] = useState(null);
12
+ const [openDropdown, setOpenDropdown] = useState(null);
11
13
  useEffect(() => {
12
14
  const handleResize = () => setIsMobile(window.innerWidth <= 590);
13
15
  handleResize();
@@ -15,22 +17,67 @@ export const Navbar = ({ siteUrl, items, logoSrc = '', logoAlt = 'M Lhuillier Lo
15
17
  window.addEventListener('resize', handleResize);
16
18
  return () => window.removeEventListener('resize', handleResize);
17
19
  }, []);
20
+ useEffect(() => {
21
+ if (!menuOpen)
22
+ setOpenDropdown(null);
23
+ }, [menuOpen]);
18
24
  const toggleMenu = () => setMenuOpen((prev) => !prev);
19
- const toggleLogin = () => setShowLogin((prev) => !prev);
25
+ const toggleLogin = () => {
26
+ setMenuOpen(false);
27
+ setShowLogin((prev) => !prev);
28
+ };
29
+ const toggleDropdown = (name) => {
30
+ setOpenDropdown(prev => (prev === name ? null : name));
31
+ };
20
32
  const handleInternalClose = () => {
21
- setShowLogin(false);
22
- document.body.style.overflow = '';
23
- onClose?.();
33
+ setIsClosing(true);
34
+ setTimeout(() => {
35
+ setShowLogin(false);
36
+ setIsClosing(false);
37
+ document.body.style.overflow = '';
38
+ onClose?.();
39
+ }, 350);
24
40
  };
25
- 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;
26
57
  const linkPath = new URL(item.link, 'https://dummybase').pathname;
27
58
  const isActive = currentPath === linkPath;
28
- return (_jsx("a", { href: item.link, target: item.target || '_self', className: `navbar-link ${isActive ? 'active' : ''}`, onClick: () => setMenuOpen(false), children: item.name.includes('-')
29
- ? item.name
30
- .split('-')
31
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
32
- .join(' & ')
33
- : item.name }, item.name));
34
- }) }), _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, 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 })] }) })) }))] }));
35
82
  };
36
83
  export default Navbar;
@@ -145,13 +145,14 @@
145
145
  font-size: 20px;
146
146
  line-height: 1;
147
147
  cursor: pointer;
148
- padding: 5px 15px;
148
+ padding: 5px 10px;
149
149
  border-radius: 6px;
150
150
  color: #111827;
151
+ margin-right: 7px;
151
152
  }
152
153
 
153
154
  .spb-close-btn:hover {
154
- background: rgba(0, 0, 0, 0.03);
155
+ background: rgb(255 0 0 / 27%);
155
156
  }
156
157
 
157
158
  /* Handle (mobile) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ml-ui-lib",
3
- "version": "1.0.24",
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",