tee3apps-cms-sdk-react 0.0.1
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.
- package/.env +11 -0
- package/README.md +255 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +13 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +33 -0
- package/rollup.config.js +43 -0
- package/src/Components/BoxRenderer.tsx +108 -0
- package/src/Components/ComponentRenderer.tsx +29 -0
- package/src/Components/ImageComponent.tsx +68 -0
- package/src/Components/RowComponent.tsx +66 -0
- package/src/Components/TextComponent.tsx +47 -0
- package/src/ErrorBoundary.tsx +35 -0
- package/src/Page.tsx +124 -0
- package/src/PageComponents/BoxComponent.tsx +397 -0
- package/src/PageComponents/RowComponent.tsx +113 -0
- package/src/PageComponents/Visual-Components/CarouselComponent.tsx +366 -0
- package/src/PageComponents/Visual-Components/GroupBrandComponent.tsx +391 -0
- package/src/PageComponents/Visual-Components/GroupCategoryComponent.tsx +425 -0
- package/src/PageComponents/Visual-Components/GroupImageList.tsx +669 -0
- package/src/PageComponents/Visual-Components/GroupProductComponent.tsx +671 -0
- package/src/PageComponents/Visual-Components/GroupVideoList.tsx +590 -0
- package/src/PageComponents/Visual-Components/ImageComponent.tsx +163 -0
- package/src/PageComponents/Visual-Components/LinkComponent.tsx +68 -0
- package/src/PageComponents/Visual-Components/LottieComponent.tsx +213 -0
- package/src/PageComponents/Visual-Components/NavigationComponent.tsx +178 -0
- package/src/PageComponents/Visual-Components/Styles/ProductListViewOne.tsx +102 -0
- package/src/PageComponents/Visual-Components/Styles/ProductListViewTwo.tsx +104 -0
- package/src/PageComponents/Visual-Components/Styles/product-list-view-one.css +166 -0
- package/src/PageComponents/Visual-Components/Styles/product-list-view-two.css +182 -0
- package/src/PageComponents/Visual-Components/TabComponent.tsx +1169 -0
- package/src/PageComponents/Visual-Components/TextComponent.tsx +114 -0
- package/src/PageComponents/Visual-Components/VideoComponent.tsx +191 -0
- package/src/PageComponents/Visual-Components/tab.css +697 -0
- package/src/common.interface.ts +216 -0
- package/src/const.ts +6 -0
- package/src/env.d.ts +15 -0
- package/src/index.css +82 -0
- package/src/index.ts +2 -0
- package/src/types.ts +234 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import '../../../src/index.css';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Linodeurl } from '../../const';
|
|
4
|
+
|
|
5
|
+
interface ImageData {
|
|
6
|
+
isDynamic: boolean;
|
|
7
|
+
shape: string;
|
|
8
|
+
url: string;
|
|
9
|
+
alt: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ImageModeProps {
|
|
13
|
+
borderRadius: number;
|
|
14
|
+
width: string;
|
|
15
|
+
height: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ImageComponentProps {
|
|
19
|
+
images: any;
|
|
20
|
+
mode: {
|
|
21
|
+
web: ImageModeProps;
|
|
22
|
+
mobileweb: ImageModeProps;
|
|
23
|
+
mobileapp: ImageModeProps;
|
|
24
|
+
tablet: ImageModeProps;
|
|
25
|
+
};
|
|
26
|
+
linktype: string;
|
|
27
|
+
link: {
|
|
28
|
+
url: string;
|
|
29
|
+
target: string;
|
|
30
|
+
};
|
|
31
|
+
product: {
|
|
32
|
+
_id: string;
|
|
33
|
+
code: string;
|
|
34
|
+
name: object;
|
|
35
|
+
pd_type: string;
|
|
36
|
+
};
|
|
37
|
+
page: {
|
|
38
|
+
_id: string;
|
|
39
|
+
code: string;
|
|
40
|
+
name: object;
|
|
41
|
+
pg_type: string;
|
|
42
|
+
facet_params: any[];
|
|
43
|
+
track_params: any[];
|
|
44
|
+
};
|
|
45
|
+
tag: {
|
|
46
|
+
_id: string;
|
|
47
|
+
code: string;
|
|
48
|
+
name: object;
|
|
49
|
+
tagtype: string;
|
|
50
|
+
};
|
|
51
|
+
type: any[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ImageComponentMainProps {
|
|
55
|
+
props: ImageComponentProps;
|
|
56
|
+
deviceMode?: string;
|
|
57
|
+
boxHeight?: string; // Add this prop to receive box height
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const ImageComponent: React.FC<ImageComponentMainProps> = ({
|
|
61
|
+
props,
|
|
62
|
+
deviceMode = 'web',
|
|
63
|
+
boxHeight = '280px',
|
|
64
|
+
}) => {
|
|
65
|
+
|
|
66
|
+
const bannerLink = (element: any): string | null => {
|
|
67
|
+
if (!element || !element.linktype) return null;
|
|
68
|
+
|
|
69
|
+
switch (element.linktype) {
|
|
70
|
+
case 'NONE':
|
|
71
|
+
return null;
|
|
72
|
+
|
|
73
|
+
case 'EXTERNAL':
|
|
74
|
+
case 'EXTERNAL_LINK':
|
|
75
|
+
return element.link?.url || null;
|
|
76
|
+
|
|
77
|
+
case 'PRODUCT': {
|
|
78
|
+
const pdType = element.product?.pd_type;
|
|
79
|
+
const code =
|
|
80
|
+
element.product?.pd_id?.code ||
|
|
81
|
+
element.product?.code ||
|
|
82
|
+
element.product?.product_data?.code;
|
|
83
|
+
|
|
84
|
+
if (!code) return null;
|
|
85
|
+
|
|
86
|
+
return pdType === 'VARIANT'
|
|
87
|
+
? `/variant/${code}`
|
|
88
|
+
: `model/${code}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
case 'TAG': {
|
|
92
|
+
const tagCode = element.tag?.code;
|
|
93
|
+
if (!tagCode) return null;
|
|
94
|
+
// return `/${tagCode}/s?type=tag`;
|
|
95
|
+
return `/tag/${tagCode}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
case 'PAGE': {
|
|
99
|
+
const pageId = element.page?._id || element.page?.code;
|
|
100
|
+
const pgType = element.page?.pg_type;
|
|
101
|
+
if (!pageId || !pgType) return null;
|
|
102
|
+
return `/page/${pageId}?type=${pgType}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
default:
|
|
106
|
+
return element.slug || null;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const getCurrentMode = () => {
|
|
111
|
+
switch (deviceMode) {
|
|
112
|
+
case 'mobileweb':
|
|
113
|
+
return props.mode.mobileweb;
|
|
114
|
+
case 'mobileapp':
|
|
115
|
+
return props.mode.mobileapp;
|
|
116
|
+
case 'tablet':
|
|
117
|
+
return props.mode.tablet;
|
|
118
|
+
case 'web':
|
|
119
|
+
default:
|
|
120
|
+
return props.mode.web;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const currentMode = getCurrentMode();
|
|
125
|
+
const containerHeight = boxHeight === 'auto' ? 'auto' : boxHeight;
|
|
126
|
+
const link = bannerLink(props);
|
|
127
|
+
const imageUrl = `${Linodeurl}${props.images?.url || props.images?.all?.url}`;
|
|
128
|
+
const altText = props.images.alt || '';
|
|
129
|
+
|
|
130
|
+
const imageElement = (
|
|
131
|
+
<img
|
|
132
|
+
src={imageUrl}
|
|
133
|
+
alt={altText}
|
|
134
|
+
className="fitted-image"
|
|
135
|
+
style={{ cursor: link ? 'pointer' : 'default' }}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div
|
|
141
|
+
className="image-box"
|
|
142
|
+
style={{
|
|
143
|
+
borderRadius: `${currentMode.borderRadius}px`,
|
|
144
|
+
height: containerHeight,
|
|
145
|
+
overflow: 'hidden',
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
{link ? (
|
|
149
|
+
<a
|
|
150
|
+
href={link}
|
|
151
|
+
target={props?.linktype=='EXTERNAL' ? props?.link?.target :'_self'}
|
|
152
|
+
style={{ display: 'block', width: '100%', height: '100%' }}
|
|
153
|
+
>
|
|
154
|
+
{imageElement}
|
|
155
|
+
</a>
|
|
156
|
+
) : (
|
|
157
|
+
imageElement
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export default ImageComponent;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface LinkProps {
|
|
4
|
+
text: any;
|
|
5
|
+
url: string;
|
|
6
|
+
target: string;
|
|
7
|
+
framename: string;
|
|
8
|
+
style?: {
|
|
9
|
+
fontSize?: number;
|
|
10
|
+
fontStyle?: {
|
|
11
|
+
isBold?: boolean;
|
|
12
|
+
isItalic?: boolean;
|
|
13
|
+
isUnderLine?: boolean;
|
|
14
|
+
isStrikeThrough?: boolean;
|
|
15
|
+
};
|
|
16
|
+
fontColor?: string;
|
|
17
|
+
textAlign?: React.CSSProperties['textAlign'];
|
|
18
|
+
fontFamily?: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface LinkComponentProps {
|
|
23
|
+
props: LinkProps;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const LinkComponent: React.FC<LinkComponentProps> = ({ props }) => {
|
|
27
|
+
const { text, url, target, style } = props;
|
|
28
|
+
|
|
29
|
+
const linkStyle: React.CSSProperties = {
|
|
30
|
+
fontSize: style?.fontSize ? `${style.fontSize}px` : '16px',
|
|
31
|
+
fontWeight: style?.fontStyle?.isBold ? 'bold' : 'normal',
|
|
32
|
+
fontStyle: style?.fontStyle?.isItalic ? 'italic' : 'normal',
|
|
33
|
+
textDecoration: style?.fontStyle?.isUnderLine ? 'underline' : 'none',
|
|
34
|
+
textDecorationLine: style?.fontStyle?.isStrikeThrough ? 'line-through' : 'none',
|
|
35
|
+
color: style?.fontColor || '#000000ff',
|
|
36
|
+
textAlign: (style?.textAlign as React.CSSProperties['textAlign']) || 'left',
|
|
37
|
+
fontFamily: style?.fontFamily || 'inherit',
|
|
38
|
+
margin: 5,
|
|
39
|
+
wordWrap: "break-word" as const,
|
|
40
|
+
overflowWrap: "break-word" as const,
|
|
41
|
+
maxWidth: "100%",
|
|
42
|
+
whiteSpace: "normal" as const,
|
|
43
|
+
width: "100%",
|
|
44
|
+
boxSizing: "border-box" as const,
|
|
45
|
+
wordBreak: "break-word" as const,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div style={{
|
|
50
|
+
display: "block" as const,
|
|
51
|
+
width: "100%",
|
|
52
|
+
maxWidth: "100%",
|
|
53
|
+
wordWrap: "break-word" as const,
|
|
54
|
+
overflowWrap: "break-word" as const,
|
|
55
|
+
boxSizing: "border-box" as const,
|
|
56
|
+
}}>
|
|
57
|
+
<a
|
|
58
|
+
href={url}
|
|
59
|
+
target={target}
|
|
60
|
+
style={linkStyle}
|
|
61
|
+
>
|
|
62
|
+
{text}
|
|
63
|
+
</a>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default LinkComponent;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import Lottie from 'lottie-react'
|
|
3
|
+
import { Linodeurl } from '../../const'
|
|
4
|
+
|
|
5
|
+
// No custom JSX intrinsic elements needed when using lottie-react
|
|
6
|
+
|
|
7
|
+
interface LottieModeProps {
|
|
8
|
+
autoplay: boolean
|
|
9
|
+
loop: boolean
|
|
10
|
+
height: string
|
|
11
|
+
width: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface LottieComponentProps {
|
|
15
|
+
lottieurl: string
|
|
16
|
+
linktype: string
|
|
17
|
+
link: {
|
|
18
|
+
url: string
|
|
19
|
+
target: string
|
|
20
|
+
}
|
|
21
|
+
product: {
|
|
22
|
+
_id: string
|
|
23
|
+
code: string
|
|
24
|
+
name: object
|
|
25
|
+
pd_type: string
|
|
26
|
+
}
|
|
27
|
+
page: {
|
|
28
|
+
_id: string
|
|
29
|
+
code: string
|
|
30
|
+
name: object
|
|
31
|
+
pg_type: string
|
|
32
|
+
facet_params: any[]
|
|
33
|
+
track_params: any[]
|
|
34
|
+
}
|
|
35
|
+
tag: {
|
|
36
|
+
_id: string
|
|
37
|
+
code: string
|
|
38
|
+
name: object
|
|
39
|
+
tagtype: string
|
|
40
|
+
}
|
|
41
|
+
mode: {
|
|
42
|
+
web: LottieModeProps
|
|
43
|
+
mobileweb: LottieModeProps
|
|
44
|
+
mobileapp: LottieModeProps
|
|
45
|
+
tablet: LottieModeProps
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface LottieComponentMainProps {
|
|
50
|
+
props: LottieComponentProps
|
|
51
|
+
deviceMode?: 'web' | 'mobileweb' | 'mobileapp' | 'tablet'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const LottieComponent: React.FC<LottieComponentMainProps> = ({ props, deviceMode = 'web' }) => {
|
|
55
|
+
const bannerLink = (element: any): string | null => {
|
|
56
|
+
if (!element || !element.linktype) return null;
|
|
57
|
+
|
|
58
|
+
switch (element.linktype) {
|
|
59
|
+
case 'NONE':
|
|
60
|
+
return null;
|
|
61
|
+
|
|
62
|
+
case 'EXTERNAL':
|
|
63
|
+
case 'EXTERNAL_LINK':
|
|
64
|
+
return element.link?.url || null;
|
|
65
|
+
|
|
66
|
+
case 'PRODUCT': {
|
|
67
|
+
const pdType = element.product?.pd_type;
|
|
68
|
+
const code =
|
|
69
|
+
element.product?.pd_id?.code ||
|
|
70
|
+
element.product?.code ||
|
|
71
|
+
element.product?.product_data?.code;
|
|
72
|
+
|
|
73
|
+
if (!code) return null;
|
|
74
|
+
|
|
75
|
+
return pdType === 'VARIANT'
|
|
76
|
+
? `/${code}/s?type=variant`
|
|
77
|
+
: `/${code}/s?type=model`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
case 'TAG': {
|
|
81
|
+
const tagCode = element.tag?.code;
|
|
82
|
+
if (!tagCode) return null;
|
|
83
|
+
return `/${tagCode}/s?type=tag`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
case 'PAGE': {
|
|
87
|
+
const pageId = element.page?._id || element.page?.code;
|
|
88
|
+
const pgType = element.page?.pg_type?.toLowerCase();
|
|
89
|
+
if (!pageId || !pgType) return null;
|
|
90
|
+
return `/page/${pageId}?type=${pgType}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
default:
|
|
94
|
+
return element.slug || null;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
const link = bannerLink(props)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
const getCurrentMode = (): LottieModeProps => {
|
|
103
|
+
switch (deviceMode) {
|
|
104
|
+
case 'mobileweb':
|
|
105
|
+
return props.mode.mobileweb
|
|
106
|
+
case 'mobileapp':
|
|
107
|
+
return props.mode.mobileapp
|
|
108
|
+
case 'tablet':
|
|
109
|
+
return props.mode.tablet
|
|
110
|
+
case 'web':
|
|
111
|
+
default:
|
|
112
|
+
return props.mode.web
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const currentMode = getCurrentMode()
|
|
117
|
+
|
|
118
|
+
const src = props.lottieurl?.startsWith('http')
|
|
119
|
+
? props.lottieurl
|
|
120
|
+
: `${Linodeurl}${props.lottieurl}`
|
|
121
|
+
|
|
122
|
+
const isClickable = bannerLink(props) !== null
|
|
123
|
+
|
|
124
|
+
const [animationData, setAnimationData] = React.useState<any | null>(null)
|
|
125
|
+
const [error, setError] = React.useState<string | null>(null)
|
|
126
|
+
|
|
127
|
+
React.useEffect(() => {
|
|
128
|
+
let isMounted = true
|
|
129
|
+
const controller = new AbortController()
|
|
130
|
+
setAnimationData(null)
|
|
131
|
+
setError(null)
|
|
132
|
+
fetch(src, { signal: controller.signal })
|
|
133
|
+
.then(async (res) => {
|
|
134
|
+
if (!res.ok) throw new Error(`Failed to load lottie json: ${res.status}`)
|
|
135
|
+
return res.json()
|
|
136
|
+
})
|
|
137
|
+
.then((json) => {
|
|
138
|
+
if (isMounted) setAnimationData(json)
|
|
139
|
+
})
|
|
140
|
+
.catch((e) => {
|
|
141
|
+
if (isMounted && e.name !== 'AbortError') setError(e.message || 'Failed to load animation')
|
|
142
|
+
})
|
|
143
|
+
return () => {
|
|
144
|
+
isMounted = false
|
|
145
|
+
controller.abort()
|
|
146
|
+
}
|
|
147
|
+
}, [src])
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<>
|
|
151
|
+
{link?(
|
|
152
|
+
<a
|
|
153
|
+
href={link}
|
|
154
|
+
target={props?.linktype=='EXTERNAL' ? props?.link?.target :'_self'}
|
|
155
|
+
>
|
|
156
|
+
<div
|
|
157
|
+
style={{
|
|
158
|
+
width: '100%',
|
|
159
|
+
height: '100%',
|
|
160
|
+
display: 'inline-block',
|
|
161
|
+
backgroundColor: 'white',
|
|
162
|
+
cursor: isClickable ? 'pointer' : 'default',
|
|
163
|
+
}}
|
|
164
|
+
|
|
165
|
+
>
|
|
166
|
+
{animationData && (
|
|
167
|
+
<Lottie
|
|
168
|
+
animationData={animationData}
|
|
169
|
+
loop={currentMode.loop}
|
|
170
|
+
autoplay={currentMode.autoplay}
|
|
171
|
+
style={{ width: '100%', height: '100%' }}
|
|
172
|
+
/>
|
|
173
|
+
)}
|
|
174
|
+
{!animationData && !error && (
|
|
175
|
+
<div style={{ width: '100%', height: '100%' }} />
|
|
176
|
+
)}
|
|
177
|
+
{error && (
|
|
178
|
+
<div style={{ fontSize: '12px', color: '#999' }}>Failed to load animation</div>
|
|
179
|
+
)}
|
|
180
|
+
</div>
|
|
181
|
+
</a>
|
|
182
|
+
):(
|
|
183
|
+
<div
|
|
184
|
+
style={{
|
|
185
|
+
width: '100%',
|
|
186
|
+
height: '100%',
|
|
187
|
+
display: 'inline-block',
|
|
188
|
+
backgroundColor: 'white',
|
|
189
|
+
cursor: isClickable ? 'pointer' : 'default',
|
|
190
|
+
}}
|
|
191
|
+
|
|
192
|
+
>
|
|
193
|
+
{animationData && (
|
|
194
|
+
<Lottie
|
|
195
|
+
animationData={animationData}
|
|
196
|
+
loop={currentMode.loop}
|
|
197
|
+
autoplay={currentMode.autoplay}
|
|
198
|
+
style={{ width: '100%', height: '100%' }}
|
|
199
|
+
/>
|
|
200
|
+
)}
|
|
201
|
+
{!animationData && !error && (
|
|
202
|
+
<div style={{ width: '100%', height: '100%' }} />
|
|
203
|
+
)}
|
|
204
|
+
{error && (
|
|
205
|
+
<div style={{ fontSize: '12px', color: '#999' }}>Failed to load animation</div>
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
208
|
+
)}
|
|
209
|
+
</>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export default LottieComponent
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
interface NavTitle {
|
|
4
|
+
all: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface NavItem {
|
|
8
|
+
nl_id?: string
|
|
9
|
+
id?: string
|
|
10
|
+
title: any
|
|
11
|
+
url?: string
|
|
12
|
+
target?: string
|
|
13
|
+
framename?: string
|
|
14
|
+
children?: NavItem[]
|
|
15
|
+
collapsed?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ColorPair {
|
|
19
|
+
background: string
|
|
20
|
+
text: string
|
|
21
|
+
arrow?: string
|
|
22
|
+
separator?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface LayerStyle {
|
|
26
|
+
isSeparator: boolean
|
|
27
|
+
color: ColorPair
|
|
28
|
+
colorHover: ColorPair
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface NavigationStyles {
|
|
32
|
+
primary: LayerStyle
|
|
33
|
+
secondary: LayerStyle
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface NavigationProps {
|
|
37
|
+
navData: NavItem[]
|
|
38
|
+
shape: 'simple' | string
|
|
39
|
+
styles: NavigationStyles
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface NavigationComponentMainProps {
|
|
43
|
+
props: NavigationProps
|
|
44
|
+
boxHeight: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const NavigationComponent: React.FC<NavigationComponentMainProps> = ({ props, boxHeight }) => {
|
|
48
|
+
const root = props.navData?.[0]
|
|
49
|
+
const topItems: NavItem[] = root?.children || []
|
|
50
|
+
|
|
51
|
+
const [openId, setOpenId] = React.useState<string | null>(null)
|
|
52
|
+
|
|
53
|
+
const buildHref = (item: NavItem) => item.url || '#'
|
|
54
|
+
|
|
55
|
+
const Item = ({ item, depth = 0, isRoot = false }: { item: NavItem; depth?: number; isRoot?: boolean }) => {
|
|
56
|
+
const hasChildren = Array.isArray(item.children) && item.children.length > 0
|
|
57
|
+
const isTop = depth === 0 && !isRoot
|
|
58
|
+
const layer = isRoot ? props.styles.primary : (isTop ? props.styles.primary : props.styles.secondary)
|
|
59
|
+
|
|
60
|
+
const baseStyles: React.CSSProperties = {
|
|
61
|
+
display: 'flex',
|
|
62
|
+
alignItems: 'center',
|
|
63
|
+
justifyContent: 'space-between',
|
|
64
|
+
gap: '8px',
|
|
65
|
+
padding: isRoot ? '12px 16px' : (isTop ? '10px 14px' : '8px 12px'),
|
|
66
|
+
color: layer.color.text,
|
|
67
|
+
background: layer.color.background,
|
|
68
|
+
textDecoration: 'none',
|
|
69
|
+
whiteSpace: 'nowrap',
|
|
70
|
+
fontSize: isRoot ? 16 : (isTop ? 14 : 13),
|
|
71
|
+
borderBottom: layer.isSeparator && !isTop && !isRoot ? `1px solid ${layer.color.separator}` : 'none',
|
|
72
|
+
borderRadius: isRoot ? 8 : (isTop ? 6 : 4),
|
|
73
|
+
cursor: 'pointer',
|
|
74
|
+
fontWeight: isRoot ? 'bold' : 'normal'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const [hover, setHover] = React.useState(false)
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div
|
|
81
|
+
style={{ position: 'relative' }}
|
|
82
|
+
onMouseEnter={() => { setHover(true); if (isTop || isRoot) setOpenId(item.id || item.nl_id || null) }}
|
|
83
|
+
onMouseLeave={() => { setHover(false); if (isTop || isRoot) setOpenId((prev) => (prev === (item.id || item.nl_id) ? null : prev)) }}
|
|
84
|
+
>
|
|
85
|
+
{isRoot ? (
|
|
86
|
+
<div
|
|
87
|
+
style={{
|
|
88
|
+
...baseStyles,
|
|
89
|
+
background: hover ? layer.colorHover.background : layer.color.background,
|
|
90
|
+
color: hover ? layer.colorHover.text : layer.color.text,
|
|
91
|
+
cursor: 'default'
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
<span>{item?.title}</span>
|
|
95
|
+
{hasChildren && (
|
|
96
|
+
<span style={{ color: layer.color.arrow || layer.color.text }}>{'▾'}</span>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
) : (
|
|
100
|
+
<a
|
|
101
|
+
href={buildHref(item)}
|
|
102
|
+
target={item.target || '_self'}
|
|
103
|
+
rel={item.target === '_blank' ? 'noopener noreferrer' : undefined}
|
|
104
|
+
style={{
|
|
105
|
+
...baseStyles,
|
|
106
|
+
background: hover ? layer.colorHover.background : layer.color.background,
|
|
107
|
+
color: hover ? layer.colorHover.text : layer.color.text
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<span>{item?.title}</span>
|
|
111
|
+
{hasChildren && (
|
|
112
|
+
<span style={{ color: layer.color.arrow || layer.color.text }}>{isTop ? '▾' : '›'}</span>
|
|
113
|
+
)}
|
|
114
|
+
</a>
|
|
115
|
+
)}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
{/* Dropdown */}
|
|
119
|
+
{hasChildren && ((isTop || isRoot) ? openId === (item.id || item.nl_id) : hover) && (
|
|
120
|
+
<div
|
|
121
|
+
style={{
|
|
122
|
+
position: 'absolute',
|
|
123
|
+
top: (isTop || isRoot) ? '100%' : 0,
|
|
124
|
+
left: (isTop || isRoot) ? 0 : '100%',
|
|
125
|
+
background: props.styles.secondary.color.background,
|
|
126
|
+
color: props.styles.secondary.color.text,
|
|
127
|
+
border: `1px solid ${props.styles.secondary.color.separator || 'transparent'}`,
|
|
128
|
+
borderRadius: 8,
|
|
129
|
+
minWidth: 220,
|
|
130
|
+
padding: 6,
|
|
131
|
+
boxShadow: '0 6px 24px rgba(0,0,0,0.12)',
|
|
132
|
+
zIndex: 1000
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
136
|
+
{item.children!.map((child) => (
|
|
137
|
+
<Item key={child.id || child.nl_id} item={child} depth={depth + 1} />
|
|
138
|
+
))}
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!root) {
|
|
147
|
+
return (
|
|
148
|
+
<div style={{ padding: 12, color: '#666' }}>No navigation items</div>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<nav
|
|
154
|
+
style={{
|
|
155
|
+
width: '100%',
|
|
156
|
+
display: 'flex',
|
|
157
|
+
alignItems: 'start',
|
|
158
|
+
paddingTop: '5px',
|
|
159
|
+
paddingLeft:'10px',
|
|
160
|
+
margin: '0px',
|
|
161
|
+
background: props.styles.primary.color.background,
|
|
162
|
+
borderRadius: 8,
|
|
163
|
+
overflow: 'visible',
|
|
164
|
+
height: boxHeight
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
{/* Render root node first */}
|
|
168
|
+
<Item key={root.id || root.nl_id} item={root} depth={0} isRoot={true} />
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
</nav>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export default NavigationComponent
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|