@datlv-trustshop/shopify-inapp-components 0.1.9

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.
Files changed (74) hide show
  1. package/README.md +141 -0
  2. package/dist/components/AppList.d.ts +13 -0
  3. package/dist/components/AppList.js +64 -0
  4. package/dist/components/ArticleList.d.ts +20 -0
  5. package/dist/components/ArticleList.js +174 -0
  6. package/dist/components/ArticleSlide.d.ts +14 -0
  7. package/dist/components/ArticleSlide.js +151 -0
  8. package/dist/components/FooterBanner.d.ts +10 -0
  9. package/dist/components/FooterBanner.js +72 -0
  10. package/dist/components/GrowApps.d.ts +13 -0
  11. package/dist/components/GrowApps.js +213 -0
  12. package/dist/components/ImageLoading.d.ts +15 -0
  13. package/dist/components/ImageLoading.js +66 -0
  14. package/dist/components/PartnerList.d.ts +9 -0
  15. package/dist/components/PartnerList.js +102 -0
  16. package/dist/components/PopupBanner.d.ts +12 -0
  17. package/dist/components/PopupBanner.js +100 -0
  18. package/dist/components/TopBanner.d.ts +14 -0
  19. package/dist/components/TopBanner.js +31 -0
  20. package/dist/components/WhatsNew.d.ts +14 -0
  21. package/dist/components/WhatsNew.js +258 -0
  22. package/dist/components/index.d.ts +9 -0
  23. package/dist/components/index.js +9 -0
  24. package/dist/components/inlineStyles.d.ts +110 -0
  25. package/dist/components/inlineStyles.js +114 -0
  26. package/dist/components/styles.d.ts +152 -0
  27. package/dist/components/styles.js +158 -0
  28. package/dist/core/adapter.d.ts +6 -0
  29. package/dist/core/adapter.js +301 -0
  30. package/dist/core/engine.d.ts +33 -0
  31. package/dist/core/engine.js +176 -0
  32. package/dist/core/fetcher.d.ts +4 -0
  33. package/dist/core/fetcher.js +72 -0
  34. package/dist/core/global-manager.d.ts +99 -0
  35. package/dist/core/global-manager.js +315 -0
  36. package/dist/hooks/index.d.ts +6 -0
  37. package/dist/hooks/index.js +6 -0
  38. package/dist/hooks/useApps.d.ts +3 -0
  39. package/dist/hooks/useApps.js +18 -0
  40. package/dist/hooks/useArticles.d.ts +11 -0
  41. package/dist/hooks/useArticles.js +49 -0
  42. package/dist/hooks/useBanner.d.ts +5 -0
  43. package/dist/hooks/useBanner.js +22 -0
  44. package/dist/hooks/useDashboard.d.ts +11 -0
  45. package/dist/hooks/useDashboard.js +13 -0
  46. package/dist/hooks/useGrowApps.d.ts +10 -0
  47. package/dist/hooks/useGrowApps.js +14 -0
  48. package/dist/hooks/useTranslations.d.ts +3 -0
  49. package/dist/hooks/useTranslations.js +9 -0
  50. package/dist/hooks/useWhatsNew.d.ts +11 -0
  51. package/dist/hooks/useWhatsNew.js +34 -0
  52. package/dist/index.d.ts +18 -0
  53. package/dist/index.js +16 -0
  54. package/dist/provider/DashboardProvider.d.ts +36 -0
  55. package/dist/provider/DashboardProvider.js +184 -0
  56. package/dist/translations/default.d.ts +2 -0
  57. package/dist/translations/default.js +27 -0
  58. package/dist/types/app.d.ts +14 -0
  59. package/dist/types/app.js +1 -0
  60. package/dist/types/article.d.ts +14 -0
  61. package/dist/types/article.js +1 -0
  62. package/dist/types/banner.d.ts +22 -0
  63. package/dist/types/banner.js +1 -0
  64. package/dist/types/dashboard.d.ts +42 -0
  65. package/dist/types/dashboard.js +1 -0
  66. package/dist/types/index.d.ts +6 -0
  67. package/dist/types/index.js +6 -0
  68. package/dist/types/partner.d.ts +8 -0
  69. package/dist/types/partner.js +1 -0
  70. package/dist/types/product-update.d.ts +23 -0
  71. package/dist/types/product-update.js +1 -0
  72. package/dist/types/translations.d.ts +28 -0
  73. package/dist/types/translations.js +1 -0
  74. package/package.json +61 -0
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # @trustshop/shopify-inapp-components
2
+
3
+ React TypeScript components for Shopify in-app dashboard content management.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @trustshop/shopify-inapp-components
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```tsx
14
+ import {
15
+ DashboardProvider,
16
+ TopBanner,
17
+ useTopBanner,
18
+ } from "@trustshop/shopify-inapp-components";
19
+
20
+ function App() {
21
+ return (
22
+ <DashboardProvider
23
+ config={{
24
+ apiUrl: "https://your-api.com/dashboard",
25
+ cacheTime: 300000, // 5 minutes
26
+ }}
27
+ >
28
+ <YourDashboard />
29
+ </DashboardProvider>
30
+ );
31
+ }
32
+
33
+ function YourDashboard() {
34
+ const topBanner = useTopBanner();
35
+
36
+ return (
37
+ <TopBanner
38
+ renderBanner={(banner, handlers) => (
39
+ <Banner onDismiss={handlers.onClose}>
40
+ <h2>{banner.title}</h2>
41
+ <p>{banner.description}</p>
42
+ <button onClick={handlers.onAction}>{banner.linkText}</button>
43
+ </Banner>
44
+ )}
45
+ />
46
+ );
47
+ }
48
+ ```
49
+
50
+ ## Features
51
+
52
+ - 🎯 **Singleton Pattern**: Efficient data management with single instance
53
+ - 🔄 **Smart Caching**: Automatic cache management with TTL
54
+ - 🎨 **Flexible UI**: Render prop pattern for custom component styling
55
+ - 🔒 **Type Safe**: Full TypeScript support
56
+ - ⚛️ **React 18+**: Support for StrictMode and SSR
57
+ - 🚀 **Performance**: Optimized for production use
58
+
59
+ ## Components
60
+
61
+ ### DashboardProvider
62
+
63
+ Main provider component that manages dashboard state.
64
+
65
+ ### Banner Components
66
+
67
+ - `TopBanner` - Top notification banner
68
+ - `PopupBanner` - Modal/popup banner
69
+ - `FooterBanner` - Footer sticky banner
70
+
71
+ ### Content Components
72
+
73
+ - `AppList` - Display apps by category
74
+ - `ArticleList` - Display articles/blog posts
75
+ - `WhatsNewList` - Product updates
76
+
77
+ ## Hooks
78
+
79
+ - `useDashboard()` - Access full dashboard context
80
+ - `useTopBanner()` - Get top banner data
81
+ - `usePopupBanner()` - Get popup banner data
82
+ - `useFooterBanner()` - Get footer banner data
83
+ - `useApps()` - Access apps data
84
+ - `useArticles()` - Access articles data
85
+ - `useWhatsNew()` - Access product updates
86
+
87
+ ## Configuration
88
+
89
+ ```typescript
90
+ interface DashboardConfig {
91
+ apiUrl: string;
92
+ cacheTime?: number; // Cache TTL in ms (default: 5 minutes)
93
+ retryAttempts?: number; // Retry attempts on failure (default: 3)
94
+ retryDelay?: number; // Delay between retries in ms (default: 1000)
95
+ headers?: Record<string, string>;
96
+ }
97
+ ```
98
+
99
+ ## API Response Adapter
100
+
101
+ The SDK automatically adapts various API response formats to a consistent internal structure, supporting flexible field names and nested data.
102
+
103
+ ## Development
104
+
105
+ ### Local Testing
106
+
107
+ For UI testing and development, use the `sdk-test-app` folder:
108
+
109
+ ```bash
110
+ cd sdk-test-app
111
+ npm install
112
+ npm run dev
113
+ ```
114
+
115
+ This will start a development server at `http://localhost:3002` with:
116
+
117
+ - Real API integration (proxied to avoid CORS)
118
+ - Production-ready WhatsNew component
119
+ - Clean UI identical to deployment environment
120
+
121
+ ### Project Structure
122
+
123
+ ```
124
+ shopify-inapp-components/
125
+ ├── src/ # Component source code
126
+ ├── dist/ # Built package
127
+ ├── sdk-test-app/ # Development testing environment
128
+ └── examples/ # Usage examples
129
+ ```
130
+
131
+ ## License
132
+
133
+ MIT
134
+
135
+ ## Author
136
+
137
+ Your Name
138
+
139
+ ## Contributing
140
+
141
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import { AppItem, AppGroup } from "../types";
3
+ export interface AppListProps {
4
+ group?: AppGroup;
5
+ className?: string;
6
+ onAppClick?: (app: AppItem) => void;
7
+ showRating?: boolean;
8
+ showInstalls?: boolean;
9
+ showPrice?: boolean;
10
+ limit?: number;
11
+ gridColumns?: number;
12
+ }
13
+ export declare const AppList: React.FC<AppListProps>;
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useApps } from "../hooks/useApps";
3
+ export const AppList = ({ group, className = "", onAppClick, showRating = true, showInstalls = false, showPrice = false, limit, gridColumns = 3, }) => {
4
+ const apps = useApps(group);
5
+ const displayApps = limit ? apps.slice(0, limit) : apps;
6
+ const handleAppClick = (app) => {
7
+ if (app.link) {
8
+ window.open(app.link, "_blank");
9
+ }
10
+ onAppClick?.(app);
11
+ };
12
+ if (displayApps.length === 0) {
13
+ return (_jsx("div", { className: `app-list-empty ${className}`, children: _jsx("p", { style: { textAlign: "center", color: "#999" }, children: "No apps available" }) }));
14
+ }
15
+ return (_jsx("div", { className: `app-list ${className}`, style: {
16
+ display: "grid",
17
+ gridTemplateColumns: `repeat(${gridColumns}, 1fr)`,
18
+ gap: "20px",
19
+ }, children: displayApps.map((app) => (_jsxs("div", { className: "app-item", style: {
20
+ padding: "16px",
21
+ border: "1px solid #e0e0e0",
22
+ borderRadius: "8px",
23
+ cursor: "pointer",
24
+ transition: "box-shadow 0.2s",
25
+ backgroundColor: "#ffffff",
26
+ }, onClick: () => handleAppClick(app), onMouseEnter: (e) => {
27
+ e.currentTarget.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.1)";
28
+ }, onMouseLeave: (e) => {
29
+ e.currentTarget.style.boxShadow = "none";
30
+ }, children: [_jsxs("div", { style: {
31
+ display: "flex",
32
+ alignItems: "start",
33
+ marginBottom: "12px",
34
+ }, children: [app.icon && (_jsx("img", { src: app.icon, alt: app.title, style: {
35
+ width: "48px",
36
+ height: "48px",
37
+ borderRadius: "8px",
38
+ marginRight: "12px",
39
+ } })), _jsxs("div", { style: { flex: 1 }, children: [_jsx("h3", { style: {
40
+ margin: "0 0 4px 0",
41
+ fontSize: "16px",
42
+ fontWeight: "600",
43
+ color: "#333",
44
+ }, children: app.title }), app.featured && (_jsx("span", { style: {
45
+ display: "inline-block",
46
+ padding: "2px 6px",
47
+ backgroundColor: "#ffd700",
48
+ color: "#333",
49
+ fontSize: "10px",
50
+ fontWeight: "bold",
51
+ borderRadius: "3px",
52
+ }, children: "FEATURED" }))] })] }), _jsx("p", { style: {
53
+ margin: "0 0 12px 0",
54
+ fontSize: "14px",
55
+ color: "#666",
56
+ lineHeight: "1.4",
57
+ }, children: app.description }), _jsxs("div", { style: {
58
+ display: "flex",
59
+ alignItems: "center",
60
+ justifyContent: "space-between",
61
+ fontSize: "12px",
62
+ color: "#999",
63
+ }, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px" }, children: [showRating && app.rating && (_jsxs("div", { style: { display: "flex", alignItems: "center" }, children: [_jsx("span", { style: { color: "#ffa500", marginRight: "4px" }, children: "\u2605" }), _jsx("span", { children: app.rating.toFixed(1) })] })), showInstalls && app.installs && (_jsxs("div", { children: [app.installs.toLocaleString(), " installs"] }))] }), showPrice && (_jsx("div", { style: { fontWeight: "600", color: "#333" }, children: app.price || "Free" }))] })] }, app.id))) }));
64
+ };
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import { ArticleItem } from "../types";
3
+ export interface ArticleListProps {
4
+ limit?: number;
5
+ className?: string;
6
+ onArticleClick?: (article: ArticleItem) => void;
7
+ layout?: "slide" | "list" | "grid";
8
+ showThumbnail?: boolean;
9
+ showAuthor?: boolean;
10
+ showDate?: boolean;
11
+ currentIndex?: number;
12
+ showNavigation?: boolean;
13
+ autoPlay?: boolean;
14
+ autoPlayInterval?: number;
15
+ onSlideChange?: (index: number) => void;
16
+ renderButton?: (article: ArticleItem, handlers: {
17
+ onClick: () => void;
18
+ }) => React.ReactNode;
19
+ }
20
+ export declare const ArticleList: React.FC<ArticleListProps>;
@@ -0,0 +1,174 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Button, Box } from "@shopify/polaris";
4
+ import { ExternalIcon } from "@shopify/polaris-icons";
5
+ import { useDashboard } from "../hooks/useDashboard";
6
+ export const ArticleList = ({ limit, className = "", onArticleClick, layout = "slide", showThumbnail = true, currentIndex: externalCurrentIndex, showNavigation = true, autoPlay = false, autoPlayInterval = 5000, onSlideChange, renderButton, }) => {
7
+ const { data } = useDashboard();
8
+ const articles = data?.articles || [];
9
+ const displayArticles = limit ? articles.slice(0, limit) : articles;
10
+ const [isMobile, setIsMobile] = useState(false);
11
+ const [internalCurrentIndex, setInternalCurrentIndex] = useState(0);
12
+ const currentIndex = externalCurrentIndex !== undefined
13
+ ? externalCurrentIndex
14
+ : internalCurrentIndex;
15
+ useEffect(() => {
16
+ const checkMobile = () => {
17
+ setIsMobile(window.innerWidth <= 768);
18
+ };
19
+ checkMobile();
20
+ window.addEventListener("resize", checkMobile);
21
+ return () => window.removeEventListener("resize", checkMobile);
22
+ }, []);
23
+ useEffect(() => {
24
+ if (!autoPlay || layout !== "slide" || displayArticles.length <= 1)
25
+ return;
26
+ const interval = setInterval(() => {
27
+ const newIndex = (currentIndex + 1) % displayArticles.length;
28
+ if (externalCurrentIndex === undefined) {
29
+ setInternalCurrentIndex(newIndex);
30
+ }
31
+ onSlideChange?.(newIndex);
32
+ }, autoPlayInterval);
33
+ return () => clearInterval(interval);
34
+ }, [
35
+ autoPlay,
36
+ layout,
37
+ displayArticles.length,
38
+ currentIndex,
39
+ autoPlayInterval,
40
+ externalCurrentIndex,
41
+ onSlideChange,
42
+ ]);
43
+ const handleSlideChange = (newIndex) => {
44
+ if (externalCurrentIndex === undefined) {
45
+ setInternalCurrentIndex(newIndex);
46
+ }
47
+ onSlideChange?.(newIndex);
48
+ };
49
+ const handlePrevious = () => {
50
+ const newIndex = currentIndex === 0 ? displayArticles.length - 1 : currentIndex - 1;
51
+ handleSlideChange(newIndex);
52
+ };
53
+ const handleNext = () => {
54
+ const newIndex = (currentIndex + 1) % displayArticles.length;
55
+ handleSlideChange(newIndex);
56
+ };
57
+ const handleArticleClick = (article) => {
58
+ if (article.link) {
59
+ window.open(article.link, "_blank");
60
+ }
61
+ onArticleClick?.(article);
62
+ };
63
+ if (displayArticles.length === 0) {
64
+ return (_jsx("div", { className: className, style: { width: "100%" }, children: _jsx("p", { style: { textAlign: "center", color: "#999" }, children: "No articles available" }) }));
65
+ }
66
+ if (layout === "slide") {
67
+ let slidePercentage;
68
+ if (isMobile) {
69
+ slidePercentage = currentIndex * 90;
70
+ }
71
+ else {
72
+ const itemWidth = 100 / displayArticles.length;
73
+ slidePercentage = currentIndex * itemWidth * 1.5;
74
+ }
75
+ return (_jsxs("div", { className: className, style: {
76
+ position: "relative",
77
+ overflow: "hidden",
78
+ width: "100%",
79
+ }, children: [_jsxs("div", { style: {
80
+ overflow: "hidden",
81
+ position: "relative",
82
+ }, children: [_jsx("div", { style: {
83
+ display: "flex",
84
+ transition: "transform 0.5s ease-in-out",
85
+ gap: "20px",
86
+ willChange: "transform",
87
+ transform: `translateX(-${slidePercentage}%)`,
88
+ }, children: displayArticles.map((article) => (_jsx("div", { style: {
89
+ flex: "0 0 auto",
90
+ width: isMobile ? "90%" : "270px",
91
+ }, children: _jsxs("div", { style: {
92
+ display: "flex",
93
+ flexDirection: "column",
94
+ gap: "4px",
95
+ }, children: [_jsxs("div", { style: {
96
+ display: "flex",
97
+ flexDirection: "column",
98
+ gap: "10px",
99
+ }, children: [showThumbnail && article.imageUrl && (_jsx("div", { style: {
100
+ width: isMobile ? "100%" : "270px",
101
+ height: "152px",
102
+ overflow: "hidden",
103
+ borderRadius: "8px",
104
+ }, children: _jsx("img", { src: article.imageUrl ||
105
+ "https://asset.trustshop.io/dashboard/news-review-summary.png", alt: article.title, style: {
106
+ width: "100%",
107
+ height: "100%",
108
+ objectFit: "cover",
109
+ } }) })), article.category && (_jsx("div", { style: {
110
+ color: "var(--p-color-text-emphasis, #616161)",
111
+ }, children: _jsx("span", { style: { fontSize: "13px" }, children: article.category }) }))] }), _jsxs("div", { style: {
112
+ display: "flex",
113
+ flexDirection: "column",
114
+ gap: "6px",
115
+ }, children: [_jsx("h3", { style: {
116
+ margin: 0,
117
+ fontSize: "14px",
118
+ fontWeight: "bold",
119
+ color: "#303030",
120
+ lineHeight: "1.4",
121
+ }, children: article.title }), renderButton ? (renderButton(article, {
122
+ onClick: () => handleArticleClick(article),
123
+ })) : (_jsx(Box, { children: _jsx(Button, { icon: ExternalIcon, onClick: () => handleArticleClick(article), variant: "plain", children: article.link ? "Read article" : "Learn more" }) }))] })] }) }, article.id))) }), showNavigation && displayArticles.length > 1 && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: handlePrevious, "aria-label": "Previous slide", style: {
124
+ position: "absolute",
125
+ top: "50%",
126
+ transform: "translateY(-50%)",
127
+ left: "10px",
128
+ background: "rgba(255, 255, 255, 0.9)",
129
+ border: "1px solid #e3e3e3",
130
+ borderRadius: "50%",
131
+ width: "32px",
132
+ height: "32px",
133
+ display: "flex",
134
+ alignItems: "center",
135
+ justifyContent: "center",
136
+ cursor: "pointer",
137
+ zIndex: 10,
138
+ transition: "background-color 0.2s",
139
+ padding: 0,
140
+ }, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "#303030", children: _jsx("path", { d: "M10.5 13L5.5 8l5-5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", fill: "none" }) }) }), _jsx("button", { onClick: handleNext, "aria-label": "Next slide", style: {
141
+ position: "absolute",
142
+ top: "50%",
143
+ transform: "translateY(-50%)",
144
+ right: "10px",
145
+ background: "rgba(255, 255, 255, 0.9)",
146
+ border: "1px solid #e3e3e3",
147
+ borderRadius: "50%",
148
+ width: "32px",
149
+ height: "32px",
150
+ display: "flex",
151
+ alignItems: "center",
152
+ justifyContent: "center",
153
+ cursor: "pointer",
154
+ zIndex: 10,
155
+ transition: "background-color 0.2s",
156
+ padding: 0,
157
+ }, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "#303030", children: _jsx("path", { d: "M5.5 13l5-5-5-5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", fill: "none" }) }) })] }))] }), showNavigation && displayArticles.length > 1 && (_jsx("div", { style: {
158
+ display: "flex",
159
+ justifyContent: "center",
160
+ gap: "8px",
161
+ marginTop: "16px",
162
+ }, children: displayArticles.map((_, index) => (_jsx("button", { onClick: () => handleSlideChange(index), "aria-label": `Go to slide ${index + 1}`, style: {
163
+ width: "8px",
164
+ height: "8px",
165
+ borderRadius: "50%",
166
+ backgroundColor: index === currentIndex ? "#303030" : "#e3e3e3",
167
+ cursor: "pointer",
168
+ transition: "background-color 0.3s",
169
+ border: "none",
170
+ padding: 0,
171
+ } }, index))) }))] }));
172
+ }
173
+ return null;
174
+ };
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import { ArticleItem } from "../types";
3
+ export interface ArticleSlideProps {
4
+ limit?: number;
5
+ className?: string;
6
+ onArticleClick?: (article: ArticleItem) => void;
7
+ autoPlay?: boolean;
8
+ autoPlayInterval?: number;
9
+ showNavigation?: boolean;
10
+ renderButton?: (article: ArticleItem, handlers: {
11
+ onClick: () => void;
12
+ }) => React.ReactNode;
13
+ }
14
+ export declare const ArticleSlide: React.FC<ArticleSlideProps>;
@@ -0,0 +1,151 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { useDashboard } from "../hooks/useDashboard";
4
+ export const ArticleSlide = ({ limit, className = "", onArticleClick, autoPlay = false, autoPlayInterval = 5000, showNavigation = true, renderButton, }) => {
5
+ const { data } = useDashboard();
6
+ const articles = data?.articles || [];
7
+ const displayArticles = limit ? articles.slice(0, limit) : articles;
8
+ const [currentIndex, setCurrentIndex] = useState(0);
9
+ const [isMobile, setIsMobile] = useState(false);
10
+ useEffect(() => {
11
+ const checkMobile = () => {
12
+ setIsMobile(window.innerWidth <= 768);
13
+ };
14
+ checkMobile();
15
+ window.addEventListener("resize", checkMobile);
16
+ return () => window.removeEventListener("resize", checkMobile);
17
+ }, []);
18
+ useEffect(() => {
19
+ if (!autoPlay || displayArticles.length <= 1)
20
+ return;
21
+ const interval = setInterval(() => {
22
+ setCurrentIndex((prev) => (prev + 1) % displayArticles.length);
23
+ }, autoPlayInterval);
24
+ return () => clearInterval(interval);
25
+ }, [autoPlay, autoPlayInterval, displayArticles.length]);
26
+ const handleArticleClick = (article) => {
27
+ if (article.link) {
28
+ window.open(article.link, "_blank");
29
+ }
30
+ onArticleClick?.(article);
31
+ };
32
+ const handlePrevious = () => {
33
+ setCurrentIndex((prev) => prev === 0 ? displayArticles.length - 1 : prev - 1);
34
+ };
35
+ const handleNext = () => {
36
+ setCurrentIndex((prev) => (prev + 1) % displayArticles.length);
37
+ };
38
+ if (displayArticles.length === 0) {
39
+ return (_jsx("div", { className: className, style: { width: "100%" }, children: _jsx("p", { style: { textAlign: "center", color: "#999" }, children: "No articles available" }) }));
40
+ }
41
+ let slidePercentage;
42
+ if (isMobile) {
43
+ slidePercentage = currentIndex * 90;
44
+ }
45
+ else {
46
+ const itemWidth = 100 / displayArticles.length;
47
+ slidePercentage = currentIndex * itemWidth * 1.5;
48
+ }
49
+ return (_jsxs("div", { className: className, style: {
50
+ position: "relative",
51
+ overflow: "hidden",
52
+ width: "100%",
53
+ }, children: [_jsxs("div", { style: {
54
+ overflow: "hidden",
55
+ position: "relative",
56
+ }, children: [_jsx("div", { style: {
57
+ display: "flex",
58
+ transition: "transform 0.5s ease-in-out",
59
+ gap: "20px",
60
+ willChange: "transform",
61
+ transform: `translateX(-${slidePercentage}%)`,
62
+ }, children: displayArticles.map((article) => (_jsx("div", { style: {
63
+ flex: "0 0 auto",
64
+ width: isMobile ? "90%" : "270px",
65
+ }, children: _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "4px" }, children: [_jsxs("div", { style: {
66
+ display: "flex",
67
+ flexDirection: "column",
68
+ gap: "10px",
69
+ }, children: [article.thumbnail && (_jsx("div", { style: {
70
+ width: isMobile ? "100%" : "270px",
71
+ height: "152px",
72
+ overflow: "hidden",
73
+ borderRadius: "8px",
74
+ }, children: _jsx("img", { src: article.thumbnail ||
75
+ "https://asset.trustshop.io/dashboard/news-review-summary.png", alt: article.title, style: {
76
+ width: "100%",
77
+ height: "100%",
78
+ objectFit: "cover",
79
+ } }) })), article.category && (_jsx("div", { style: { color: "var(--p-color-text-emphasis, #616161)" }, children: _jsx("span", { style: { fontSize: "13px" }, children: article.category }) }))] }), _jsxs("div", { style: {
80
+ display: "flex",
81
+ flexDirection: "column",
82
+ gap: "6px",
83
+ }, children: [_jsx("h3", { style: {
84
+ margin: 0,
85
+ fontSize: "14px",
86
+ fontWeight: "bold",
87
+ color: "#303030",
88
+ lineHeight: "1.4",
89
+ }, children: article.title }), renderButton ? (renderButton(article, {
90
+ onClick: () => handleArticleClick(article),
91
+ })) : (_jsxs("button", { onClick: () => handleArticleClick(article), style: {
92
+ background: "none",
93
+ border: "none",
94
+ color: "var(--p-color-text, #303030)",
95
+ cursor: "pointer",
96
+ padding: "4px 0",
97
+ fontSize: "14px",
98
+ textAlign: "left",
99
+ display: "inline-flex",
100
+ alignItems: "center",
101
+ gap: "4px",
102
+ }, children: [article.link ? "Read article" : "Learn more", _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "currentColor", children: [_jsx("path", { d: "M17 2a1 1 0 0 1 1 1v4a1 1 0 1 1-2 0V4.414l-7.293 7.293a1 1 0 0 1-1.414-1.414L14.586 3H12a1 1 0 1 1 0-2h5Z" }), _jsx("path", { d: "M3 6a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2H5v10h10v-4a1 1 0 1 1 2 0v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6Z" })] })] }))] })] }) }, article.id))) }), showNavigation && displayArticles.length > 1 && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: handlePrevious, "aria-label": "Previous slide", style: {
103
+ position: "absolute",
104
+ top: "50%",
105
+ transform: "translateY(-50%)",
106
+ left: "10px",
107
+ background: "rgba(255, 255, 255, 0.9)",
108
+ border: "1px solid #e3e3e3",
109
+ borderRadius: "50%",
110
+ width: "32px",
111
+ height: "32px",
112
+ display: "flex",
113
+ alignItems: "center",
114
+ justifyContent: "center",
115
+ cursor: "pointer",
116
+ zIndex: 10,
117
+ transition: "background-color 0.2s",
118
+ padding: 0,
119
+ }, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "#303030", children: _jsx("path", { d: "M10.5 13L5.5 8l5-5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", fill: "none" }) }) }), _jsx("button", { onClick: handleNext, "aria-label": "Next slide", style: {
120
+ position: "absolute",
121
+ top: "50%",
122
+ transform: "translateY(-50%)",
123
+ right: "10px",
124
+ background: "rgba(255, 255, 255, 0.9)",
125
+ border: "1px solid #e3e3e3",
126
+ borderRadius: "50%",
127
+ width: "32px",
128
+ height: "32px",
129
+ display: "flex",
130
+ alignItems: "center",
131
+ justifyContent: "center",
132
+ cursor: "pointer",
133
+ zIndex: 10,
134
+ transition: "background-color 0.2s",
135
+ padding: 0,
136
+ }, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "#303030", children: _jsx("path", { d: "M5.5 13l5-5-5-5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", fill: "none" }) }) })] }))] }), showNavigation && displayArticles.length > 1 && (_jsx("div", { style: {
137
+ display: "flex",
138
+ justifyContent: "center",
139
+ gap: "8px",
140
+ marginTop: "16px",
141
+ }, children: displayArticles.map((_, index) => (_jsx("button", { onClick: () => setCurrentIndex(index), "aria-label": `Go to slide ${index + 1}`, style: {
142
+ width: "8px",
143
+ height: "8px",
144
+ borderRadius: "50%",
145
+ backgroundColor: index === currentIndex ? "#303030" : "#e3e3e3",
146
+ cursor: "pointer",
147
+ transition: "background-color 0.3s",
148
+ border: "none",
149
+ padding: 0,
150
+ } }, index))) }))] }));
151
+ };
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import { BannerItem } from "../types";
3
+ export interface FooterBannerProps {
4
+ className?: string;
5
+ onClose?: () => void;
6
+ onAction?: (banner: BannerItem) => void;
7
+ closable?: boolean;
8
+ sticky?: boolean;
9
+ }
10
+ export declare const FooterBanner: React.FC<FooterBannerProps>;