ml-ui-lib 1.0.9 → 1.0.10

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.
@@ -0,0 +1,69 @@
1
+ .accordion {
2
+ width: 100%;
3
+ font-family: "Inter", sans-serif;
4
+ color: #222;
5
+ }
6
+
7
+ .accordion-group {
8
+ margin-bottom: 1.5rem;
9
+ }
10
+
11
+ .accordion-group-title {
12
+ font-size: 1.1rem;
13
+ font-weight: 600;
14
+ color: #444;
15
+ margin-bottom: 0.5rem;
16
+ }
17
+
18
+ .accordion-item {
19
+ border: 1px solid #e5e7eb;
20
+ border-radius: 10px;
21
+ margin-bottom: 0.75rem;
22
+ overflow: hidden;
23
+ background-color: #fff;
24
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
25
+ transition: all 0.3s ease;
26
+ }
27
+
28
+ .accordion-header {
29
+ width: 100%;
30
+ background: #f9fafb;
31
+ border: none;
32
+ padding: 1rem 1.25rem;
33
+ display: flex;
34
+ justify-content: space-between;
35
+ align-items: center;
36
+ cursor: pointer;
37
+ text-align: left;
38
+ font-size: 1rem;
39
+ font-weight: 500;
40
+ transition: background 0.2s ease;
41
+ }
42
+
43
+ .accordion-header:hover {
44
+ background: #f3f4f6;
45
+ }
46
+
47
+ .accordion-icon {
48
+ transition: transform 0.3s ease;
49
+ font-size: 1rem;
50
+ color: #6b7280;
51
+ }
52
+
53
+ .accordion-icon.open {
54
+ transform: rotate(180deg);
55
+ }
56
+
57
+ .accordion-content-wrapper {
58
+ overflow: hidden;
59
+ max-height: 0;
60
+ transition: max-height 0.35s ease;
61
+ background: #fff;
62
+ }
63
+
64
+ .accordion-content {
65
+ padding: 1rem 1.25rem;
66
+ font-size: 0.95rem;
67
+ color: #555;
68
+ line-height: 1.6;
69
+ }
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import "./Accordion.css";
3
+ export interface AccordionSubItem {
4
+ title: string;
5
+ content: string;
6
+ }
7
+ export interface AccordionGroup {
8
+ title: string;
9
+ items: AccordionSubItem[];
10
+ }
11
+ export interface AccordionProps {
12
+ items: AccordionGroup[];
13
+ }
14
+ export declare const Accordion: React.FC<AccordionProps>;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useRef, useEffect } from "react";
3
+ import "./Accordion.css";
4
+ export const Accordion = ({ items }) => {
5
+ const [openIndexes, setOpenIndexes] = useState([]);
6
+ const contentRefs = useRef([]);
7
+ const toggleItem = (index) => {
8
+ setOpenIndexes((prev) => prev.includes(index) ? prev.filter((i) => i !== index) : [...prev, index]);
9
+ };
10
+ useEffect(() => {
11
+ openIndexes.forEach((index) => {
12
+ const content = contentRefs.current[index];
13
+ if (content) {
14
+ const scrollHeight = content.scrollHeight;
15
+ content.style.maxHeight = `${scrollHeight}px`;
16
+ }
17
+ });
18
+ }, [openIndexes]);
19
+ return (_jsx("div", { className: "accordion", children: items.map((group, groupIndex) => (_jsxs("div", { className: "accordion-group", children: [group.title && _jsx("h3", { className: "accordion-group-title", children: group.title }), group.items.map((item, itemIndex) => {
20
+ const index = groupIndex * 1000 + itemIndex;
21
+ const isOpen = openIndexes.includes(index);
22
+ return (_jsxs("div", { className: `accordion-item ${isOpen ? "open" : ""}`, children: [_jsxs("button", { className: "accordion-header", onClick: () => toggleItem(index), children: [_jsx("span", { className: "accordion-title", children: item.title }), _jsx("span", { className: `accordion-icon ${isOpen ? "open" : ""}`, children: "\u25BC" })] }), _jsx("div", { ref: (el) => (contentRefs.current[index] = el), className: "accordion-content-wrapper", style: {
23
+ maxHeight: isOpen
24
+ ? `${contentRefs.current[index]?.scrollHeight}px`
25
+ : "0px",
26
+ }, children: _jsx("div", { className: "accordion-content", children: item.content }) })] }, index));
27
+ })] }, groupIndex))) }));
28
+ };
@@ -0,0 +1,89 @@
1
+ .card {
2
+ background: #ffffff;
3
+ border-radius: 12px;
4
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
5
+ padding: 1.5rem;
6
+ display: flex;
7
+ flex-direction: column;
8
+ overflow: hidden;
9
+ max-width: 100%;
10
+ box-sizing: border-box;
11
+ transition: box-shadow 0.25s ease, transform 0.25s ease;
12
+ }
13
+
14
+ /* Hoverable behavior only if enabled */
15
+ .card.hoverable:hover {
16
+ transform: translateY(-3px);
17
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
18
+ cursor: pointer;
19
+ }
20
+
21
+ /* Grid container */
22
+ .card-grid {
23
+ display: grid;
24
+ gap: 1.5rem;
25
+ align-items: start;
26
+ width: 100%;
27
+ box-sizing: border-box;
28
+ overflow-wrap: break-word;
29
+ word-wrap: break-word;
30
+ word-break: break-word;
31
+ }
32
+
33
+ /* Each section */
34
+ .card-section {
35
+ display: flex;
36
+ flex-direction: column;
37
+ justify-content: flex-start;
38
+ min-width: 0;
39
+ }
40
+
41
+ /* Text elements should wrap */
42
+ .card-section p,
43
+ .card-section span,
44
+ .card-section li,
45
+ .card-section h1,
46
+ .card-section h2,
47
+ .card-section h3,
48
+ .card-section h4,
49
+ .card-section h5,
50
+ .card-section h6 {
51
+ margin: 0;
52
+ overflow-wrap: anywhere;
53
+ word-break: break-word;
54
+ white-space: normal;
55
+ }
56
+
57
+ /* Image / cover section */
58
+ .card-image-section {
59
+ padding: 0; /* remove text padding */
60
+ overflow: hidden;
61
+ border-radius: 12px;
62
+ }
63
+
64
+ .card-image-section img {
65
+ width: 100%;
66
+ height: 100%;
67
+ object-fit: cover;
68
+ display: block;
69
+ border-radius: 12px;
70
+ }
71
+
72
+ /* Responsive stacking */
73
+ @media (max-width: 992px) {
74
+ .card {
75
+ margin-bottom: 10px;
76
+ }
77
+ /* .card-grid {
78
+ grid-template-columns: 1fr !important;
79
+ } */
80
+
81
+ /* .card-section {
82
+ padding-bottom: 1rem;
83
+ border-bottom: 1px solid #eee;
84
+ } */
85
+
86
+ /* .card-section:last-child {
87
+ border-bottom: none;
88
+ } */
89
+ }
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import "./Card.css";
3
+ export interface CardProps {
4
+ children: React.ReactNode[] | React.ReactNode;
5
+ columns?: number;
6
+ columnWidths?: string[];
7
+ width?: string;
8
+ hoverable?: boolean;
9
+ className?: string;
10
+ style?: React.CSSProperties;
11
+ }
12
+ export declare const Card: React.FC<CardProps>;
13
+ export default Card;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import "./Card.css";
4
+ export const Card = ({ children, columns = 1, columnWidths, width = "100%", hoverable = false, className = "", style, }) => {
5
+ const gridTemplateColumns = columnWidths && columnWidths.length
6
+ ? columnWidths.map((w) => `minmax(0, ${w})`).join(" ")
7
+ : `repeat(${columns}, minmax(0, 1fr))`;
8
+ return (_jsx("div", { className: `card ${hoverable ? "hoverable" : ""} ${className}`, style: { width, ...style }, children: _jsx("div", { className: "card-grid", style: { gridTemplateColumns }, children: React.Children.map(children, (child, i) => (_jsx("div", { className: `card-section ${React.isValidElement(child) &&
9
+ child.props?.className?.includes("card-image")
10
+ ? "card-image-section"
11
+ : ""}`, children: child }, i))) }) }));
12
+ };
13
+ export default Card;
@@ -0,0 +1,111 @@
1
+ .expandable-panel-container {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 16px;
5
+ }
6
+
7
+ .expandable-card-row {
8
+ display: grid;
9
+ gap: 16px;
10
+ }
11
+
12
+ .expandable-card {
13
+ flex: 1;
14
+ background: #fff;
15
+ border: 1px solid #e5e7eb;
16
+ border-radius: 12px;
17
+ padding: 16px;
18
+ cursor: pointer;
19
+ transition: all 0.2s ease;
20
+ height: 100%;
21
+ display: flex;
22
+ flex-direction: column;
23
+ justify-content: space-between;
24
+ }
25
+
26
+ .expandable-card.active {
27
+ border-color: #ff4d4f;
28
+ box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.1);
29
+ }
30
+
31
+ /* --- Sliding subpanel --- */
32
+ .subpanel-wrapper {
33
+ width: 100%;
34
+ overflow: hidden;
35
+ max-height: 0;
36
+ opacity: 0;
37
+ transform: translateY(-10px);
38
+ transition:
39
+ max-height 0.4s ease,
40
+ opacity 0.3s ease,
41
+ transform 0.3s ease;
42
+ margin-top: 0;
43
+ }
44
+
45
+ .subpanel-wrapper.open {
46
+ opacity: 1;
47
+ transform: translateY(0);
48
+ margin-top: 12px;
49
+ }
50
+
51
+ .subpanel-content {
52
+ display: flex;
53
+ flex-wrap: wrap;
54
+ gap: 10px;
55
+ background: #f9fafb;
56
+ border-radius: 10px;
57
+ border: 1px solid #ddd;
58
+ padding: 16px;
59
+ }
60
+
61
+ .sub-card {
62
+ flex: 1 1 calc(33.333% - 10px);
63
+ border-radius: 8px;
64
+ /* background: white;
65
+ border: 1px solid #eee;
66
+ padding: 12px; */
67
+ display: flex;
68
+ flex-direction: column;
69
+ justify-content: center;
70
+ }
71
+
72
+ /* --- Responsive behavior --- */
73
+ @media (min-width: 993px) {
74
+ .expandable-card-wrapper {
75
+ display: flex;
76
+ }
77
+
78
+ .expandable-card-row {
79
+ grid-template-columns: repeat(3, 1fr);
80
+ }
81
+
82
+ .sub-card {
83
+ flex: 1 1 calc(33.333% - 10px);
84
+ }
85
+ }
86
+
87
+ @media (max-width: 992px) {
88
+ .expandable-card-row {
89
+ grid-template-columns: repeat(2, 1fr);
90
+ }
91
+
92
+ .sub-card {
93
+ flex: 1 1 calc(50% - 10px);
94
+ }
95
+ }
96
+
97
+ @media (max-width: 576px) {
98
+ .expandable-card-wrapper {
99
+ display: flex;
100
+ flex-direction: column;
101
+ }
102
+
103
+ .expandable-card-row {
104
+ grid-template-columns: 1fr;
105
+ gap: 8px;
106
+ }
107
+
108
+ .sub-card {
109
+ flex: 1 1 100%;
110
+ }
111
+ }
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import "./ExpandablePanel.css";
3
+ export interface ExpandablePanelItem {
4
+ id: string | number;
5
+ title: string;
6
+ content: React.ReactNode;
7
+ subPanel?: React.ReactNode | React.ReactNode[];
8
+ }
9
+ export interface ExpandablePanelProps {
10
+ items: ExpandablePanelItem[];
11
+ columns?: number;
12
+ }
13
+ export declare const ExpandablePanel: React.FC<ExpandablePanelProps>;
14
+ export default ExpandablePanel;
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useRef } from "react";
3
+ import "./ExpandablePanel.css";
4
+ export const ExpandablePanel = ({ items, columns = 3, }) => {
5
+ const [expandedId, setExpandedId] = useState(null);
6
+ const [columnCount, setColumnCount] = useState(columns);
7
+ const handleClick = (id) => {
8
+ setExpandedId(expandedId === id ? null : id);
9
+ };
10
+ // Responsive columns
11
+ useEffect(() => {
12
+ const handleResize = () => {
13
+ const width = window.innerWidth;
14
+ if (width >= 993)
15
+ setColumnCount(3);
16
+ else if (width >= 577)
17
+ setColumnCount(2);
18
+ else
19
+ setColumnCount(1);
20
+ };
21
+ handleResize();
22
+ window.addEventListener("resize", handleResize);
23
+ return () => window.removeEventListener("resize", handleResize);
24
+ }, []);
25
+ // Split into rows
26
+ const rows = [];
27
+ for (let i = 0; i < items.length; i += columnCount) {
28
+ rows.push(items.slice(i, i + columnCount));
29
+ }
30
+ return (_jsx("div", { className: "expandable-panel-container", children: rows.map((row, rowIndex) => {
31
+ const expandedItem = row.find((item) => item.id === expandedId);
32
+ return (_jsxs("div", { className: "expandable-row", children: [_jsx("div", { className: "expandable-card-row", style: { gridTemplateColumns: `repeat(${columnCount}, 1fr)` }, children: row.map((item) => (_jsxs("div", { className: "expandable-card-wrapper", children: [_jsxs("div", { className: `expandable-card ${expandedId === item.id ? "active" : ""}`, onClick: () => handleClick(item.id), children: [_jsx("h4", { children: item.title }), _jsx("div", { className: "card-content", children: item.content })] }), columnCount === 1 && (_jsx(ExpandableSubPanel, { isOpen: expandedId === item.id, content: item.subPanel, mode: "mobile" }))] }, item.id))) }), columnCount > 1 && (_jsx(ExpandableSubPanel, { isOpen: !!expandedItem, content: expandedItem?.subPanel, mode: "desktop" }))] }, rowIndex));
33
+ }) }));
34
+ };
35
+ const ExpandableSubPanel = ({ isOpen, content, mode, }) => {
36
+ const ref = useRef(null);
37
+ useEffect(() => {
38
+ const el = ref.current;
39
+ if (!el)
40
+ return;
41
+ if (isOpen) {
42
+ el.style.maxHeight = el.scrollHeight + "px";
43
+ }
44
+ else {
45
+ el.style.maxHeight = "0px";
46
+ }
47
+ }, [isOpen, content]);
48
+ if (!content)
49
+ return null;
50
+ return (_jsx("div", { ref: ref, className: `subpanel-wrapper ${isOpen ? "open" : ""} ${mode}`, children: _jsx("div", { className: "subpanel-content", children: Array.isArray(content)
51
+ ? content.map((sub, i) => (_jsx("div", { className: "sub-card", children: sub }, i)))
52
+ : content }) }));
53
+ };
54
+ export default ExpandablePanel;
@@ -0,0 +1,140 @@
1
+ .navbar {
2
+ background: #fff;
3
+ border-bottom: 1px solid #eee;
4
+ width: 100%;
5
+ z-index: 100;
6
+ top: 0;
7
+ }
8
+
9
+ .navbar-container {
10
+ display: flex;
11
+ justify-content: space-between;
12
+ align-items: center;
13
+ max-width: 1200px;
14
+ margin: 0 auto;
15
+ }
16
+
17
+ .navbar-logo img {
18
+ max-height: 50px;
19
+ }
20
+
21
+ .navbar-links {
22
+ display: flex;
23
+ gap: 1.5rem;
24
+ transition: all 0.3s ease;
25
+ margin-left: 35px;
26
+ }
27
+
28
+ .navbar-links a {
29
+ text-decoration: none;
30
+ color: #333;
31
+ font-weight: 400;
32
+ font-size: 1rem;
33
+ transition: color 0.2s;
34
+ }
35
+
36
+ .navbar-links a:hover {
37
+ color: #ff0000;
38
+ }
39
+
40
+ /* Burger Icon */
41
+ .burger {
42
+ display: none;
43
+ flex-direction: column;
44
+ justify-content: space-between;
45
+ width: 20px;
46
+ height: 18px;
47
+ border: none;
48
+ background: none;
49
+ cursor: pointer;
50
+ padding: 0;
51
+ }
52
+
53
+ .burger span {
54
+ display: block;
55
+ height: 2px;
56
+ width: 100%;
57
+ background: #333;
58
+ border-radius: 2px;
59
+ transition: all 0.3s ease;
60
+ }
61
+
62
+ .burger.active span:nth-child(1) {
63
+ transform: translateY(8px) rotate(45deg);
64
+ }
65
+
66
+ .burger.active span:nth-child(2) {
67
+ opacity: 0;
68
+ }
69
+
70
+ .burger.active span:nth-child(3) {
71
+ transform: translateY(-8px) rotate(-45deg);
72
+ }
73
+
74
+ @media (min-width: 993px) {
75
+ .navbar-container {
76
+ padding: 1rem 5rem;
77
+ }
78
+
79
+ }
80
+
81
+ /* Responsive Menu */
82
+ @media (max-width: 992px) {
83
+ .burger {
84
+ display: flex;
85
+ }
86
+
87
+ .navbar-container {
88
+ padding: 1rem 1.5rem;
89
+ }
90
+
91
+ .navbar-links {
92
+ position: absolute;
93
+ top: 70px;
94
+ right: 0;
95
+ background: #fff;
96
+ flex-direction: column;
97
+ align-items: flex-start;
98
+ width: 100%;
99
+ max-height: 0;
100
+ overflow: hidden;
101
+ opacity: 0;
102
+ transition: all 0.3s ease;
103
+ padding: 0 2rem;
104
+ }
105
+
106
+ .navbar-links.open {
107
+ max-height: 460px;
108
+ opacity: 1;
109
+ padding: 1rem 2rem;
110
+ border-bottom: 1px solid #eee;
111
+ }
112
+
113
+ .navbar-links a {
114
+ padding: 0.75rem 0;
115
+ width: 100%;
116
+ }
117
+
118
+ .navbar-logo img {
119
+ height: 35px;
120
+ }
121
+ }
122
+
123
+ /* Login Button */
124
+ .login-btn {
125
+ color: #333;
126
+ border: none;
127
+ /* padding: 0.5rem 1.25rem; */
128
+ border-radius: 6px;
129
+ cursor: pointer;
130
+ transition: background 0.3s ease;
131
+ }
132
+
133
+ .login-btn:hover {
134
+ color: #d00000;
135
+ }
136
+
137
+ .navbar-user {
138
+ color: #333;
139
+ font-weight: 500;
140
+ }
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import './Navbar.css';
3
+ export interface NavbarItem {
4
+ name: string;
5
+ link: string;
6
+ target?: '_blank' | '_self';
7
+ }
8
+ export interface NavbarProps {
9
+ items: NavbarItem[];
10
+ logoSrc?: string;
11
+ logoAlt?: string;
12
+ logoWidth?: number | string;
13
+ logoHeight?: number | string;
14
+ className?: string;
15
+ login?: boolean;
16
+ logedinData?: Record<string, any> | null;
17
+ loginContent?: React.ReactNode;
18
+ }
19
+ export declare const Navbar: React.FC<NavbarProps>;
20
+ export default Navbar;
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState, useEffect } from 'react';
4
+ import './Navbar.css';
5
+ import { Modal } from '../Modal/Modal';
6
+ import { SlidingPanelBottom } from '../SlidingPanelBottom/SlidingPanelBottom';
7
+ export const Navbar = ({ items, logoSrc = '/images/ml-logo.svg', logoAlt = 'M Lhuillier Logo', logoWidth = 220, logoHeight = 40, className = '', login = false, logedinData = null, loginContent, }) => {
8
+ const [menuOpen, setMenuOpen] = useState(false);
9
+ const [showLogin, setShowLogin] = useState(false);
10
+ const [isMobile, setIsMobile] = useState(false);
11
+ useEffect(() => {
12
+ const handleResize = () => setIsMobile(window.innerWidth <= 590);
13
+ handleResize();
14
+ window.addEventListener('resize', handleResize);
15
+ return () => window.removeEventListener('resize', handleResize);
16
+ }, []);
17
+ const toggleMenu = () => setMenuOpen((prev) => !prev);
18
+ const toggleLogin = () => setShowLogin((prev) => !prev);
19
+ return (_jsxs(_Fragment, { children: [_jsx("nav", { className: `navbar ${className}`, children: _jsxs("div", { className: "navbar-container", children: [_jsxs("div", { style: { display: 'flex', gap: '10px', alignItems: 'center' }, children: [_jsxs("button", { className: `burger ${menuOpen ? 'active' : ''}`, onClick: toggleMenu, "aria-label": "Toggle menu", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }), _jsx("div", { className: "navbar-logo", children: _jsx("a", { href: "/", className: "navbar-brand", children: _jsx("img", { src: logoSrc, alt: logoAlt, width: logoWidth, height: logoHeight, style: { cursor: 'pointer', display: 'block' } }) }) }), _jsx("div", { className: `navbar-links ${menuOpen ? 'open' : ''}`, children: items.map((item) => (_jsx("a", { href: item.link, target: item.target || '_self', rel: item.target === '_blank' ? 'noopener noreferrer' : undefined, className: "navbar-link", onClick: () => setMenuOpen(false), children: item.name.includes('-')
20
+ ? item.name
21
+ .split('-')
22
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
23
+ .join(' & ')
24
+ : item.name }, item.name))) })] }), login &&
25
+ (logedinData ? (_jsxs("div", { className: "navbar-user", children: ["Hello, ", logedinData.name || 'User'] })) : (_jsx("span", { className: "login-btn", onClick: toggleLogin, children: "Login" })))] }) }), login && !logedinData && (_jsx(_Fragment, { children: !isMobile ? (_jsx(Modal, { open: showLogin, onClose: () => setShowLogin(false), title: "", children: _jsx("div", { className: "navbar-modal-content", children: loginContent || _jsx("p", { children: "Login form goes here" }) }) })) : (_jsx("div", { className: "navbar-login-panel", children: _jsxs(SlidingPanelBottom, { isOpen: showLogin, height: "70%", onClose: () => setShowLogin(false), children: [_jsx("div", { className: "navbar-panel-header" }), _jsx("div", { className: "navbar-panel-content", children: loginContent || _jsx("p", { children: "Login form goes here" }) })] }) })) })), _jsx("style", { children: `
26
+ .navbar-login-panel .spb-backdrop {
27
+ background: rgba(255, 0, 0, 1) !important; /* darker overlay */
28
+ backdrop-filter: blur(3px);
29
+ }
30
+
31
+ .navbar-login-panel .spb-header {
32
+ padding: 0x !important;
33
+ border-bottom: none !important;
34
+ }
35
+
36
+ .navbar-login-panel .spb-backdrop::before {
37
+ content: "";
38
+ position: absolute;
39
+ top: 70px; /* distance from the top, adjust as needed */
40
+ left: 50%;
41
+ transform: translateX(-50%);
42
+ width: 280px; /* optional, adjust image size */
43
+ height: 80px; /* optional */
44
+ background: url(https://mlhuillier.com/img/revamp/ml-logo-2.svg) center/contain no-repeat;
45
+ opacity: 1;
46
+ pointer-events: none;
47
+ z-index: 1;
48
+ }
49
+ ` })] }));
50
+ };
51
+ export default Navbar;
@@ -0,0 +1,171 @@
1
+ /* ✅ Existing code kept as-is */
2
+
3
+ /* Root wrapper covers entire viewport */
4
+ .spb-root {
5
+ position: fixed;
6
+ inset: 0;
7
+ z-index: 10000;
8
+ pointer-events: none;
9
+ }
10
+
11
+ /* Backdrop */
12
+ .spb-backdrop {
13
+ position: absolute;
14
+ inset: 0;
15
+ background: rgba(0, 0, 0, 0.45);
16
+ opacity: 0;
17
+ transition: opacity 320ms ease;
18
+ pointer-events: auto;
19
+ backdrop-filter: blur(4px);
20
+ }
21
+
22
+ /* Panel container that slides up from bottom */
23
+ .spb-panel {
24
+ position: absolute;
25
+ left: 50%;
26
+ bottom: 0;
27
+ transform: translate(-50%, 100%);
28
+ transition: transform 320ms cubic-bezier(.22, .9, .32, 1);
29
+ background: #ffffff;
30
+ border-top-left-radius: 16px;
31
+ border-top-right-radius: 16px;
32
+ box-shadow: 0 -20px 40px rgba(2, 6, 23, 0.35);
33
+ max-height: 95vh;
34
+ overflow: hidden;
35
+ display: flex;
36
+ flex-direction: column;
37
+ pointer-events: auto;
38
+ width: 90%;
39
+ }
40
+
41
+ /* Handle (optional) */
42
+ .spb-handle {
43
+ width: 40px;
44
+ height: 4px;
45
+ background: #e5e7eb;
46
+ border-radius: 999px;
47
+ margin: 8px auto 0;
48
+ opacity: 0;
49
+ /* ✅ Hidden by default for desktop */
50
+ transition: opacity 0.2s ease;
51
+ }
52
+
53
+ /* Header */
54
+ .spb-header {
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: space-between;
58
+ padding: 12px 16px;
59
+ border-bottom: 1px solid #f1f1f1;
60
+ min-height: 48px;
61
+ }
62
+
63
+ .spb-title {
64
+ margin: 0;
65
+ font-size: 1.05rem;
66
+ font-weight: 600;
67
+ color: #111827;
68
+ }
69
+
70
+ .spb-close-btn {
71
+ background: none;
72
+ border: none;
73
+ font-size: 20px;
74
+ line-height: 1;
75
+ cursor: pointer;
76
+ padding: 6px;
77
+ border-radius: 6px;
78
+ color: #111827;
79
+ }
80
+
81
+ .spb-close-btn:hover {
82
+ background: rgba(0, 0, 0, 0.03);
83
+ }
84
+
85
+ /* Body */
86
+ .spb-body {
87
+ padding: 16px;
88
+ overflow-y: auto;
89
+ -webkit-overflow-scrolling: touch;
90
+ flex: 1 1 auto;
91
+ }
92
+
93
+ /* Open/close states */
94
+ .spb-root.spb-open {
95
+ pointer-events: auto;
96
+ }
97
+
98
+ .spb-root.spb-open .spb-backdrop {
99
+ opacity: 1;
100
+ }
101
+
102
+ .spb-root.spb-open .spb-panel {
103
+ transform: translate(-50%, 0);
104
+ }
105
+
106
+ .spb-root.spb-close {
107
+ pointer-events: none;
108
+ }
109
+
110
+ .spb-root.spb-close .spb-backdrop {
111
+ opacity: 0;
112
+ }
113
+
114
+ .spb-root.spb-close .spb-panel {
115
+ transform: translate(-50%, 100%);
116
+ }
117
+
118
+ @media (min-width: 993px) {
119
+ .spb-panel {
120
+ width: 90%;
121
+ }
122
+ }
123
+
124
+ /* ✅ Responsive: Large screens (≤992px) */
125
+ @media (max-width: 992px) {
126
+ .spb-panel {
127
+ width: 90%;
128
+ max-width: 960px;
129
+ height: 85vh;
130
+ border-radius: 16px 16px 0 0;
131
+ left: 50%;
132
+ bottom: 0;
133
+ transform: translate(-50%, 100%);
134
+ transition: transform 360ms cubic-bezier(.22, .9, .32, 1);
135
+ }
136
+
137
+ .spb-root.spb-open .spb-panel {
138
+ transform: translate(-50%, 0);
139
+ }
140
+
141
+ .spb-body {
142
+ flex: 1;
143
+ overflow-y: auto;
144
+ }
145
+ }
146
+
147
+ @media (max-width: 590px) {
148
+ .spb-panel {
149
+ width: 100% !important;
150
+ }
151
+
152
+ .spb-handle {
153
+ opacity: 1;
154
+ cursor: grab;
155
+ touch-action: pan-y;
156
+ }
157
+
158
+ .spb-header {
159
+ cursor: grab;
160
+ touch-action: pan-y;
161
+ }
162
+
163
+ .spb-header:active,
164
+ .spb-handle:active {
165
+ cursor: grabbing;
166
+ }
167
+
168
+ .spb-close-btn {
169
+ display: none;
170
+ }
171
+ }
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import "./SlidingPanelBottom.css";
3
+ export interface SlidingPanelBottomProps {
4
+ isOpen: boolean;
5
+ onClose: () => void;
6
+ children: React.ReactNode;
7
+ height?: string;
8
+ width?: string;
9
+ title?: string;
10
+ closeOnOverlayClick?: boolean;
11
+ }
12
+ export declare const SlidingPanelBottom: React.FC<SlidingPanelBottomProps>;
13
+ export default SlidingPanelBottom;
@@ -0,0 +1,90 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import "./SlidingPanelBottom.css";
5
+ export const SlidingPanelBottom = ({ isOpen, onClose, children, height = "80%", width = "100%", title, closeOnOverlayClick = true, }) => {
6
+ const [visible, setVisible] = useState(false);
7
+ const [animateOpen, setAnimateOpen] = useState(false);
8
+ const panelRef = useRef(null);
9
+ const headerRef = useRef(null);
10
+ const handleRef = useRef(null);
11
+ useEffect(() => {
12
+ if (isOpen) {
13
+ setVisible(true);
14
+ document.body.style.overflow = "hidden";
15
+ const id = setTimeout(() => setAnimateOpen(true), 20);
16
+ return () => clearTimeout(id);
17
+ }
18
+ else {
19
+ document.body.style.overflow = "";
20
+ setAnimateOpen(false);
21
+ const id = setTimeout(() => setVisible(false), 320);
22
+ return () => clearTimeout(id);
23
+ }
24
+ }, [isOpen]);
25
+ useEffect(() => {
26
+ const onKey = (e) => {
27
+ if (e.key === "Escape" && isOpen)
28
+ onClose();
29
+ };
30
+ document.addEventListener("keydown", onKey);
31
+ return () => document.removeEventListener("keydown", onKey);
32
+ }, [isOpen, onClose]);
33
+ // ✅ Drag gesture on both handle + header
34
+ useEffect(() => {
35
+ const panel = panelRef.current;
36
+ const handle = handleRef.current;
37
+ const header = headerRef.current;
38
+ if (!panel)
39
+ return;
40
+ const dragAreaElements = [handle, header].filter(Boolean);
41
+ let startY = 0;
42
+ let currentY = 0;
43
+ let isDragging = false;
44
+ const onTouchStart = (e) => {
45
+ startY = e.touches[0].clientY;
46
+ isDragging = true;
47
+ panel.style.transition = "none";
48
+ };
49
+ const onTouchMove = (e) => {
50
+ if (!isDragging)
51
+ return;
52
+ currentY = e.touches[0].clientY;
53
+ const diff = currentY - startY;
54
+ if (diff > 0) {
55
+ panel.style.transform = `translate(-50%, ${diff}px)`;
56
+ }
57
+ };
58
+ const onTouchEnd = () => {
59
+ if (!isDragging)
60
+ return;
61
+ isDragging = false;
62
+ const diff = currentY - startY;
63
+ if (diff > 100) {
64
+ panel.style.transition = "transform 0.3s ease";
65
+ panel.style.transform = `translate(-50%, 100%)`;
66
+ setTimeout(() => onClose(), 250);
67
+ }
68
+ else {
69
+ panel.style.transition = "transform 0.3s ease";
70
+ panel.style.transform = `translate(-50%, 0)`;
71
+ }
72
+ };
73
+ dragAreaElements.forEach((el) => {
74
+ el.addEventListener("touchstart", onTouchStart);
75
+ el.addEventListener("touchmove", onTouchMove);
76
+ el.addEventListener("touchend", onTouchEnd);
77
+ });
78
+ return () => {
79
+ dragAreaElements.forEach((el) => {
80
+ el.removeEventListener("touchstart", onTouchStart);
81
+ el.removeEventListener("touchmove", onTouchMove);
82
+ el.removeEventListener("touchend", onTouchEnd);
83
+ });
84
+ };
85
+ }, [onClose]);
86
+ if (!visible && !isOpen)
87
+ return null;
88
+ return (_jsxs("div", { className: `spb-root ${animateOpen ? "spb-open" : "spb-close"}`, "aria-hidden": !isOpen, children: [_jsx("div", { className: "spb-backdrop", onClick: () => closeOnOverlayClick && onClose() }), _jsxs("div", { ref: panelRef, className: "spb-panel", style: { height, width }, role: "dialog", "aria-modal": "true", onClick: (e) => e.stopPropagation(), children: [_jsx("div", { ref: handleRef, className: "spb-handle" }), _jsxs("div", { ref: headerRef, className: "spb-header", children: [title ? _jsx("h3", { className: "spb-title", children: title }) : _jsx("div", {}), _jsx("button", { className: "spb-close-btn", onClick: onClose, children: "\u00D7" })] }), _jsx("div", { className: "spb-body", children: children })] })] }));
89
+ };
90
+ export default SlidingPanelBottom;
@@ -0,0 +1,82 @@
1
+ .table-wrapper {
2
+ overflow-x: auto;
3
+ width: 100%;
4
+ }
5
+
6
+ /* Base table styles */
7
+ .custom-table {
8
+ width: 100%;
9
+ border-collapse: collapse;
10
+ border-radius: 8px;
11
+ font-family: inherit;
12
+ font-size: 14px;
13
+ color: #111827;
14
+ transition: background 0.2s ease;
15
+ }
16
+
17
+ /* Lines option */
18
+ .custom-table.with-lines th,
19
+ .custom-table.with-lines td {
20
+ border: 1px solid #e5e7eb;
21
+ }
22
+
23
+ .custom-table.no-lines th,
24
+ .custom-table.no-lines td {
25
+ border: none;
26
+ }
27
+
28
+ /* Header */
29
+ .custom-table thead tr {
30
+ background-color: var(--head-color);
31
+ color: var(--head-text-color);
32
+ }
33
+
34
+ .custom-table th {
35
+ padding: 12px 16px;
36
+ text-align: left;
37
+ font-weight: 600;
38
+ font-size: 14px;
39
+ white-space: nowrap;
40
+ }
41
+
42
+ /* Body rows */
43
+ .custom-table tbody tr {
44
+ background-color: var(--alternate-row-color);
45
+ transition: background 0.2s ease;
46
+ }
47
+
48
+ .custom-table tbody tr:nth-child(odd) {
49
+ background-color: #ffffff;
50
+ }
51
+
52
+ .custom-table tbody tr:hover {
53
+ background-color: var(--hover-color);
54
+ }
55
+
56
+ /* Cells */
57
+ .custom-table td {
58
+ padding: 12px 16px;
59
+ vertical-align: middle;
60
+ white-space: nowrap;
61
+ overflow: hidden;
62
+ text-overflow: ellipsis;
63
+ }
64
+
65
+ /* Actions column */
66
+ .actions-header {
67
+ text-align: right;
68
+ }
69
+
70
+ .actions-cell {
71
+ text-align: right;
72
+ white-space: nowrap;
73
+ }
74
+
75
+ /* Responsive tweaks */
76
+ @media (max-width: 768px) {
77
+ .custom-table th,
78
+ .custom-table td {
79
+ padding: 10px 12px;
80
+ font-size: 13px;
81
+ }
82
+ }
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import "./Table.css";
3
+ export interface TableProps {
4
+ columns: string[];
5
+ data: Record<string, any>[];
6
+ renderActions?: (row: Record<string, any>, rowIndex: number) => React.ReactNode;
7
+ hoverColor?: string;
8
+ headColor?: string;
9
+ headTextColor?: string;
10
+ alternateRowColor?: string;
11
+ lines?: boolean;
12
+ style?: React.CSSProperties;
13
+ }
14
+ export declare const Table: React.FC<TableProps>;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "./Table.css";
3
+ export const Table = ({ columns, data, renderActions, hoverColor = "#f9fafb", headColor = "#f3f4f6", headTextColor = "#000000", alternateRowColor = "#ffffff", lines = true, style, }) => {
4
+ return (_jsx("div", { className: "table-wrapper", style: style, children: _jsxs("table", { className: `custom-table ${lines ? "with-lines" : "no-lines"}`, style: {
5
+ "--hover-color": hoverColor,
6
+ "--head-color": headColor,
7
+ "--head-text-color": headTextColor,
8
+ "--alternate-row-color": alternateRowColor,
9
+ }, children: [_jsx("thead", { children: _jsxs("tr", { children: [columns.map((col, i) => (_jsx("th", { children: col }, i))), renderActions && _jsx("th", { className: "actions-header", children: "Actions" })] }) }), _jsx("tbody", { children: data.map((row, rowIndex) => (_jsxs("tr", { children: [columns.map((col, i) => (_jsx("td", { children: row[col] }, i))), renderActions && (_jsx("td", { className: "actions-cell", children: renderActions(row, rowIndex) }))] }, rowIndex))) })] }) }));
10
+ };
package/dist/index.d.ts CHANGED
@@ -13,3 +13,15 @@ export { DatePicker2 } from "./components/DatePicker2/DatePicker2";
13
13
  export type { DatePicker2Props, DateValue } from "./components/DatePicker2/DatePicker2";
14
14
  export { DatePicker } from "./components/DatePicker/DatePicker";
15
15
  export type { DatePickerProps, DatePickerValue } from "./components/DatePicker/DatePicker";
16
+ export { SlidingPanelBottom } from "./components/SlidingPanelBottom/SlidingPanelBottom";
17
+ export type { SlidingPanelBottomProps } from "./components/SlidingPanelBottom/SlidingPanelBottom";
18
+ export { Card } from "./components/Card/Card";
19
+ export type { CardProps } from "./components/Card/Card";
20
+ export { Table } from "./components/Table/Table";
21
+ export type { TableProps } from "./components/Table/Table";
22
+ export { ExpandablePanel } from "./components/ExpandablePanel/ExpandablePanel";
23
+ export type { ExpandablePanelProps, ExpandablePanelItem } from "./components/ExpandablePanel/ExpandablePanel";
24
+ export { Accordion } from "./components/Accordion/Accordion";
25
+ export type { AccordionProps } from "./components/Accordion/Accordion";
26
+ export { Navbar } from "./components/Navbar/Navbar";
27
+ export type { NavbarProps, NavbarItem } from "./components/Navbar/Navbar";
package/dist/index.js CHANGED
@@ -6,3 +6,9 @@ export { PhoneInput } from "./components/PhoneInput/PhoneInput";
6
6
  export { Dropdown } from "./components/Dropdown/Dropdown";
7
7
  export { DatePicker2 } from "./components/DatePicker2/DatePicker2";
8
8
  export { DatePicker } from "./components/DatePicker/DatePicker";
9
+ export { SlidingPanelBottom } from "./components/SlidingPanelBottom/SlidingPanelBottom";
10
+ export { Card } from "./components/Card/Card";
11
+ export { Table } from "./components/Table/Table";
12
+ export { ExpandablePanel } from "./components/ExpandablePanel/ExpandablePanel";
13
+ export { Accordion } from "./components/Accordion/Accordion";
14
+ export { Navbar } from "./components/Navbar/Navbar";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ml-ui-lib",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.esm.js",
6
6
  "types": "dist/index.d.ts",