@gravity-ai/react 0.1.0

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/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # @gravity-ai/react
2
+
3
+ React components for rendering Gravity AI advertisements with automatic impression and click tracking.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @gravity-ai/react
9
+ ```
10
+
11
+ > **Note:** This package has a peer dependency on React 17+.
12
+
13
+ ## Quick Start
14
+
15
+ ```tsx
16
+ import { Client } from '@gravity-ai/api';
17
+ import { AdBanner } from '@gravity-ai/react';
18
+ import { useEffect, useState } from 'react';
19
+
20
+ const client = new Client('your-api-key');
21
+
22
+ function ChatMessage({ messages }) {
23
+ const [ad, setAd] = useState(null);
24
+
25
+ useEffect(() => {
26
+ client.getAd({ messages }).then(setAd);
27
+ }, [messages]);
28
+
29
+ return (
30
+ <div>
31
+ {/* Your chat content */}
32
+ <AdBanner ad={ad} theme="dark" size="medium" />
33
+ </div>
34
+ );
35
+ }
36
+ ```
37
+
38
+ ## Components
39
+
40
+ ### `<AdBanner />`
41
+
42
+ A fully-styled, customizable ad banner component.
43
+
44
+ ```tsx
45
+ import { AdBanner } from '@gravity-ai/react';
46
+
47
+ <AdBanner
48
+ ad={adResponse}
49
+ theme="dark" // 'light' | 'dark' | 'minimal' | 'branded'
50
+ size="medium" // 'small' | 'medium' | 'large' | 'responsive'
51
+ showLabel={true} // Show "Sponsored" label
52
+ labelText="Ad" // Custom label text
53
+ openInNewTab={true} // Open click URL in new tab
54
+ onImpression={() => console.log('Impression tracked')}
55
+ onClickTracked={() => console.log('Click tracked')}
56
+ />
57
+ ```
58
+
59
+ #### Props
60
+
61
+ | Prop | Type | Default | Description |
62
+ |------|------|---------|-------------|
63
+ | `ad` | `AdResponse \| null` | required | The ad response from Gravity API |
64
+ | `theme` | `'light' \| 'dark' \| 'minimal' \| 'branded'` | `'light'` | Visual theme preset |
65
+ | `size` | `'small' \| 'medium' \| 'large' \| 'responsive'` | `'medium'` | Size preset |
66
+ | `className` | `string` | - | Custom class name for container |
67
+ | `style` | `CSSProperties` | - | Custom inline styles |
68
+ | `showLabel` | `boolean` | `true` | Show "Sponsored" label |
69
+ | `labelText` | `string` | `'Sponsored'` | Custom label text |
70
+ | `fallback` | `ReactNode` | `null` | Content when ad is null |
71
+ | `openInNewTab` | `boolean` | `true` | Open link in new tab |
72
+ | `backgroundColor` | `string` | - | Custom background (overrides theme) |
73
+ | `textColor` | `string` | - | Custom text color (overrides theme) |
74
+ | `accentColor` | `string` | - | Custom accent color |
75
+ | `borderRadius` | `number \| string` | - | Custom border radius |
76
+ | `disableImpressionTracking` | `boolean` | `false` | Disable auto impression tracking |
77
+ | `onClick` | `() => void` | - | Custom click handler |
78
+ | `onImpression` | `() => void` | - | Callback when impression fires |
79
+ | `onClickTracked` | `() => void` | - | Callback when click is tracked |
80
+
81
+ ### `<AdText />`
82
+
83
+ A minimal text-only component for full styling control.
84
+
85
+ ```tsx
86
+ import { AdText } from '@gravity-ai/react';
87
+
88
+ <AdText
89
+ ad={adResponse}
90
+ className="my-custom-class"
91
+ style={{ color: 'blue' }}
92
+ />
93
+ ```
94
+
95
+ #### Props
96
+
97
+ | Prop | Type | Default | Description |
98
+ |------|------|---------|-------------|
99
+ | `ad` | `AdResponse \| null` | required | The ad response from Gravity API |
100
+ | `className` | `string` | - | Custom class name |
101
+ | `style` | `CSSProperties` | - | Custom inline styles |
102
+ | `fallback` | `ReactNode` | `null` | Content when ad is null |
103
+ | `openInNewTab` | `boolean` | `true` | Open link in new tab |
104
+ | `disableImpressionTracking` | `boolean` | `false` | Disable auto tracking |
105
+ | `onClick` | `() => void` | - | Custom click handler |
106
+ | `onImpression` | `() => void` | - | Callback on impression |
107
+ | `onClickTracked` | `() => void` | - | Callback on click |
108
+
109
+ ## Hooks
110
+
111
+ ### `useAdTracking`
112
+
113
+ For building custom ad components with automatic tracking.
114
+
115
+ ```tsx
116
+ import { useAdTracking } from '@gravity-ai/react';
117
+
118
+ function CustomAdComponent({ ad }) {
119
+ const { handleClick } = useAdTracking({
120
+ ad,
121
+ onImpression: () => console.log('Viewed'),
122
+ onClickTracked: () => console.log('Clicked'),
123
+ });
124
+
125
+ return (
126
+ <a href={ad.clickUrl} onClick={handleClick}>
127
+ {ad.adText}
128
+ </a>
129
+ );
130
+ }
131
+ ```
132
+
133
+ ## Theming Examples
134
+
135
+ ### Dark Theme
136
+ ```tsx
137
+ <AdBanner ad={ad} theme="dark" />
138
+ ```
139
+
140
+ ### Custom Colors
141
+ ```tsx
142
+ <AdBanner
143
+ ad={ad}
144
+ backgroundColor="#1e1b4b"
145
+ textColor="#e0e7ff"
146
+ accentColor="#818cf8"
147
+ />
148
+ ```
149
+
150
+ ### Minimal (Inherit Parent Styles)
151
+ ```tsx
152
+ <AdBanner ad={ad} theme="minimal" showLabel={false} />
153
+ ```
154
+
155
+ ## TypeScript
156
+
157
+ Full TypeScript support with exported types:
158
+
159
+ ```tsx
160
+ import type { AdResponse, AdBannerProps, AdTheme, AdSize } from '@gravity-ai/react';
161
+ ```
162
+
163
+ ## License
164
+
165
+ MIT
166
+
@@ -0,0 +1,160 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { CSSProperties, ReactNode } from 'react';
3
+
4
+ /**
5
+ * Ad response from the Gravity API
6
+ * This mirrors the type from @gravity-ai/api for convenience
7
+ */
8
+ interface AdResponse {
9
+ adText: string;
10
+ impUrl?: string;
11
+ clickUrl?: string;
12
+ payout?: number;
13
+ }
14
+ /**
15
+ * Visual theme presets for the ad banner
16
+ */
17
+ type AdTheme = 'light' | 'dark' | 'minimal' | 'branded';
18
+ /**
19
+ * Banner size presets
20
+ */
21
+ type AdSize = 'small' | 'medium' | 'large' | 'responsive';
22
+ /**
23
+ * Props for the AdBanner component
24
+ */
25
+ interface AdBannerProps {
26
+ /** The ad response from Gravity API */
27
+ ad: AdResponse | null;
28
+ /** Visual theme preset */
29
+ theme?: AdTheme;
30
+ /** Size preset */
31
+ size?: AdSize;
32
+ /** Custom class name for the container */
33
+ className?: string;
34
+ /** Custom inline styles for the container */
35
+ style?: CSSProperties;
36
+ /** Custom styles for the ad text */
37
+ textStyle?: CSSProperties;
38
+ /** Custom class name for the ad text */
39
+ textClassName?: string;
40
+ /** Whether to show the "Sponsored" label */
41
+ showLabel?: boolean;
42
+ /** Custom label text (default: "Sponsored") */
43
+ labelText?: string;
44
+ /** Custom styles for the label */
45
+ labelStyle?: CSSProperties;
46
+ /** Custom click handler (called in addition to tracking) */
47
+ onClick?: () => void;
48
+ /** Callback when impression is tracked */
49
+ onImpression?: () => void;
50
+ /** Callback when click is tracked */
51
+ onClickTracked?: () => void;
52
+ /** Custom content to render when ad is null */
53
+ fallback?: ReactNode;
54
+ /** Whether to disable automatic impression tracking */
55
+ disableImpressionTracking?: boolean;
56
+ /** Whether to open link in new tab (default: true) */
57
+ openInNewTab?: boolean;
58
+ /** Custom border radius */
59
+ borderRadius?: number | string;
60
+ /** Custom background color (overrides theme) */
61
+ backgroundColor?: string;
62
+ /** Custom text color (overrides theme) */
63
+ textColor?: string;
64
+ /** Custom accent/brand color */
65
+ accentColor?: string;
66
+ }
67
+ /**
68
+ * Props for the AdText component (minimal text-only rendering)
69
+ */
70
+ interface AdTextProps {
71
+ /** The ad response from Gravity API */
72
+ ad: AdResponse | null;
73
+ /** Custom class name */
74
+ className?: string;
75
+ /** Custom inline styles */
76
+ style?: CSSProperties;
77
+ /** Custom click handler */
78
+ onClick?: () => void;
79
+ /** Callback when impression is tracked */
80
+ onImpression?: () => void;
81
+ /** Callback when click is tracked */
82
+ onClickTracked?: () => void;
83
+ /** Content to render when ad is null */
84
+ fallback?: ReactNode;
85
+ /** Whether to disable automatic impression tracking */
86
+ disableImpressionTracking?: boolean;
87
+ /** Whether to open link in new tab (default: true) */
88
+ openInNewTab?: boolean;
89
+ }
90
+
91
+ /**
92
+ * AdBanner - A customizable component for rendering Gravity AI advertisements
93
+ *
94
+ * @example
95
+ * ```tsx
96
+ * import { AdBanner } from '@gravity-ai/react';
97
+ *
98
+ * function MyComponent() {
99
+ * const [ad, setAd] = useState(null);
100
+ *
101
+ * useEffect(() => {
102
+ * client.getAd({ messages }).then(setAd);
103
+ * }, []);
104
+ *
105
+ * return (
106
+ * <AdBanner
107
+ * ad={ad}
108
+ * theme="dark"
109
+ * size="medium"
110
+ * showLabel
111
+ * />
112
+ * );
113
+ * }
114
+ * ```
115
+ */
116
+ declare function AdBanner({ ad, theme, size, className, style, textStyle, textClassName, showLabel, labelText, labelStyle, onClick, onImpression, onClickTracked, fallback, disableImpressionTracking, openInNewTab, borderRadius, backgroundColor, textColor, accentColor, }: AdBannerProps): react_jsx_runtime.JSX.Element;
117
+ declare namespace AdBanner {
118
+ var displayName: string;
119
+ }
120
+
121
+ /**
122
+ * AdText - A minimal text-only component for rendering Gravity AI advertisements
123
+ *
124
+ * Use this when you want full control over styling and just need the ad text
125
+ * with automatic tracking.
126
+ *
127
+ * @example
128
+ * ```tsx
129
+ * import { AdText } from '@gravity-ai/react';
130
+ *
131
+ * function MyComponent() {
132
+ * return (
133
+ * <AdText
134
+ * ad={ad}
135
+ * className="my-custom-ad-style"
136
+ * />
137
+ * );
138
+ * }
139
+ * ```
140
+ */
141
+ declare function AdText({ ad, className, style, onClick, onImpression, onClickTracked, fallback, disableImpressionTracking, openInNewTab, }: AdTextProps): react_jsx_runtime.JSX.Element;
142
+ declare namespace AdText {
143
+ var displayName: string;
144
+ }
145
+
146
+ interface UseAdTrackingOptions {
147
+ ad: AdResponse | null;
148
+ disableImpressionTracking?: boolean;
149
+ onImpression?: () => void;
150
+ onClickTracked?: () => void;
151
+ }
152
+ /**
153
+ * Hook to handle ad impression and click tracking
154
+ */
155
+ declare function useAdTracking({ ad, disableImpressionTracking, onImpression, onClickTracked, }: UseAdTrackingOptions): {
156
+ handleClick: () => void;
157
+ impressionTracked: boolean;
158
+ };
159
+
160
+ export { AdBanner, type AdBannerProps, type AdResponse, type AdSize, AdText, type AdTextProps, type AdTheme, useAdTracking };
@@ -0,0 +1,160 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { CSSProperties, ReactNode } from 'react';
3
+
4
+ /**
5
+ * Ad response from the Gravity API
6
+ * This mirrors the type from @gravity-ai/api for convenience
7
+ */
8
+ interface AdResponse {
9
+ adText: string;
10
+ impUrl?: string;
11
+ clickUrl?: string;
12
+ payout?: number;
13
+ }
14
+ /**
15
+ * Visual theme presets for the ad banner
16
+ */
17
+ type AdTheme = 'light' | 'dark' | 'minimal' | 'branded';
18
+ /**
19
+ * Banner size presets
20
+ */
21
+ type AdSize = 'small' | 'medium' | 'large' | 'responsive';
22
+ /**
23
+ * Props for the AdBanner component
24
+ */
25
+ interface AdBannerProps {
26
+ /** The ad response from Gravity API */
27
+ ad: AdResponse | null;
28
+ /** Visual theme preset */
29
+ theme?: AdTheme;
30
+ /** Size preset */
31
+ size?: AdSize;
32
+ /** Custom class name for the container */
33
+ className?: string;
34
+ /** Custom inline styles for the container */
35
+ style?: CSSProperties;
36
+ /** Custom styles for the ad text */
37
+ textStyle?: CSSProperties;
38
+ /** Custom class name for the ad text */
39
+ textClassName?: string;
40
+ /** Whether to show the "Sponsored" label */
41
+ showLabel?: boolean;
42
+ /** Custom label text (default: "Sponsored") */
43
+ labelText?: string;
44
+ /** Custom styles for the label */
45
+ labelStyle?: CSSProperties;
46
+ /** Custom click handler (called in addition to tracking) */
47
+ onClick?: () => void;
48
+ /** Callback when impression is tracked */
49
+ onImpression?: () => void;
50
+ /** Callback when click is tracked */
51
+ onClickTracked?: () => void;
52
+ /** Custom content to render when ad is null */
53
+ fallback?: ReactNode;
54
+ /** Whether to disable automatic impression tracking */
55
+ disableImpressionTracking?: boolean;
56
+ /** Whether to open link in new tab (default: true) */
57
+ openInNewTab?: boolean;
58
+ /** Custom border radius */
59
+ borderRadius?: number | string;
60
+ /** Custom background color (overrides theme) */
61
+ backgroundColor?: string;
62
+ /** Custom text color (overrides theme) */
63
+ textColor?: string;
64
+ /** Custom accent/brand color */
65
+ accentColor?: string;
66
+ }
67
+ /**
68
+ * Props for the AdText component (minimal text-only rendering)
69
+ */
70
+ interface AdTextProps {
71
+ /** The ad response from Gravity API */
72
+ ad: AdResponse | null;
73
+ /** Custom class name */
74
+ className?: string;
75
+ /** Custom inline styles */
76
+ style?: CSSProperties;
77
+ /** Custom click handler */
78
+ onClick?: () => void;
79
+ /** Callback when impression is tracked */
80
+ onImpression?: () => void;
81
+ /** Callback when click is tracked */
82
+ onClickTracked?: () => void;
83
+ /** Content to render when ad is null */
84
+ fallback?: ReactNode;
85
+ /** Whether to disable automatic impression tracking */
86
+ disableImpressionTracking?: boolean;
87
+ /** Whether to open link in new tab (default: true) */
88
+ openInNewTab?: boolean;
89
+ }
90
+
91
+ /**
92
+ * AdBanner - A customizable component for rendering Gravity AI advertisements
93
+ *
94
+ * @example
95
+ * ```tsx
96
+ * import { AdBanner } from '@gravity-ai/react';
97
+ *
98
+ * function MyComponent() {
99
+ * const [ad, setAd] = useState(null);
100
+ *
101
+ * useEffect(() => {
102
+ * client.getAd({ messages }).then(setAd);
103
+ * }, []);
104
+ *
105
+ * return (
106
+ * <AdBanner
107
+ * ad={ad}
108
+ * theme="dark"
109
+ * size="medium"
110
+ * showLabel
111
+ * />
112
+ * );
113
+ * }
114
+ * ```
115
+ */
116
+ declare function AdBanner({ ad, theme, size, className, style, textStyle, textClassName, showLabel, labelText, labelStyle, onClick, onImpression, onClickTracked, fallback, disableImpressionTracking, openInNewTab, borderRadius, backgroundColor, textColor, accentColor, }: AdBannerProps): react_jsx_runtime.JSX.Element;
117
+ declare namespace AdBanner {
118
+ var displayName: string;
119
+ }
120
+
121
+ /**
122
+ * AdText - A minimal text-only component for rendering Gravity AI advertisements
123
+ *
124
+ * Use this when you want full control over styling and just need the ad text
125
+ * with automatic tracking.
126
+ *
127
+ * @example
128
+ * ```tsx
129
+ * import { AdText } from '@gravity-ai/react';
130
+ *
131
+ * function MyComponent() {
132
+ * return (
133
+ * <AdText
134
+ * ad={ad}
135
+ * className="my-custom-ad-style"
136
+ * />
137
+ * );
138
+ * }
139
+ * ```
140
+ */
141
+ declare function AdText({ ad, className, style, onClick, onImpression, onClickTracked, fallback, disableImpressionTracking, openInNewTab, }: AdTextProps): react_jsx_runtime.JSX.Element;
142
+ declare namespace AdText {
143
+ var displayName: string;
144
+ }
145
+
146
+ interface UseAdTrackingOptions {
147
+ ad: AdResponse | null;
148
+ disableImpressionTracking?: boolean;
149
+ onImpression?: () => void;
150
+ onClickTracked?: () => void;
151
+ }
152
+ /**
153
+ * Hook to handle ad impression and click tracking
154
+ */
155
+ declare function useAdTracking({ ad, disableImpressionTracking, onImpression, onClickTracked, }: UseAdTrackingOptions): {
156
+ handleClick: () => void;
157
+ impressionTracked: boolean;
158
+ };
159
+
160
+ export { AdBanner, type AdBannerProps, type AdResponse, type AdSize, AdText, type AdTextProps, type AdTheme, useAdTracking };
package/dist/index.js ADDED
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AdBanner: () => AdBanner,
24
+ AdText: () => AdText,
25
+ useAdTracking: () => useAdTracking
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/components/AdBanner.tsx
30
+ var import_react2 = require("react");
31
+
32
+ // src/hooks/useAdTracking.ts
33
+ var import_react = require("react");
34
+ function useAdTracking({
35
+ ad,
36
+ disableImpressionTracking = false,
37
+ onImpression,
38
+ onClickTracked
39
+ }) {
40
+ const impressionTracked = (0, import_react.useRef)(false);
41
+ (0, import_react.useEffect)(() => {
42
+ if (!ad || !ad.impUrl || disableImpressionTracking || impressionTracked.current) {
43
+ return;
44
+ }
45
+ const trackImpression = async () => {
46
+ try {
47
+ const img = new Image();
48
+ img.src = ad.impUrl;
49
+ impressionTracked.current = true;
50
+ onImpression?.();
51
+ } catch (error) {
52
+ console.error("[Gravity] Failed to track impression:", error);
53
+ }
54
+ };
55
+ trackImpression();
56
+ }, [ad, disableImpressionTracking, onImpression]);
57
+ (0, import_react.useEffect)(() => {
58
+ impressionTracked.current = false;
59
+ }, [ad?.impUrl]);
60
+ const handleClick = (0, import_react.useCallback)(() => {
61
+ if (!ad?.clickUrl) return;
62
+ onClickTracked?.();
63
+ }, [ad?.clickUrl, onClickTracked]);
64
+ return {
65
+ handleClick,
66
+ impressionTracked: impressionTracked.current
67
+ };
68
+ }
69
+
70
+ // src/styles.ts
71
+ var baseContainerStyle = {
72
+ display: "block",
73
+ textDecoration: "none",
74
+ cursor: "pointer",
75
+ transition: "all 0.2s ease",
76
+ boxSizing: "border-box",
77
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
78
+ };
79
+ var themeStyles = {
80
+ light: {
81
+ backgroundColor: "#ffffff",
82
+ color: "#1a1a1a",
83
+ border: "1px solid #e5e5e5",
84
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.08)"
85
+ },
86
+ dark: {
87
+ backgroundColor: "#1a1a1a",
88
+ color: "#f5f5f5",
89
+ border: "1px solid #333333",
90
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.3)"
91
+ },
92
+ minimal: {
93
+ backgroundColor: "transparent",
94
+ color: "inherit",
95
+ border: "none",
96
+ boxShadow: "none"
97
+ },
98
+ branded: {
99
+ backgroundColor: "#6366f1",
100
+ color: "#ffffff",
101
+ border: "none",
102
+ boxShadow: "0 2px 8px rgba(99, 102, 241, 0.3)"
103
+ }
104
+ };
105
+ var sizeStyles = {
106
+ small: {
107
+ padding: "8px 12px",
108
+ fontSize: "13px",
109
+ lineHeight: "1.4",
110
+ borderRadius: "6px"
111
+ },
112
+ medium: {
113
+ padding: "12px 16px",
114
+ fontSize: "14px",
115
+ lineHeight: "1.5",
116
+ borderRadius: "8px"
117
+ },
118
+ large: {
119
+ padding: "16px 20px",
120
+ fontSize: "16px",
121
+ lineHeight: "1.6",
122
+ borderRadius: "10px"
123
+ },
124
+ responsive: {
125
+ padding: "clamp(8px, 2vw, 16px) clamp(12px, 3vw, 20px)",
126
+ fontSize: "clamp(13px, 1.5vw, 16px)",
127
+ lineHeight: "1.5",
128
+ borderRadius: "clamp(6px, 1vw, 10px)"
129
+ }
130
+ };
131
+ var baseLabelStyle = {
132
+ fontSize: "10px",
133
+ fontWeight: 600,
134
+ textTransform: "uppercase",
135
+ letterSpacing: "0.5px",
136
+ opacity: 0.7,
137
+ marginBottom: "4px",
138
+ display: "block"
139
+ };
140
+ function getAdBannerStyles(theme, size, customStyles) {
141
+ const combined = {
142
+ ...baseContainerStyle,
143
+ ...themeStyles[theme],
144
+ ...sizeStyles[size]
145
+ };
146
+ if (customStyles?.backgroundColor) {
147
+ combined.backgroundColor = customStyles.backgroundColor;
148
+ }
149
+ if (customStyles?.textColor) {
150
+ combined.color = customStyles.textColor;
151
+ }
152
+ if (customStyles?.borderRadius !== void 0) {
153
+ combined.borderRadius = customStyles.borderRadius;
154
+ }
155
+ if (customStyles?.style) {
156
+ Object.assign(combined, customStyles.style);
157
+ }
158
+ return combined;
159
+ }
160
+
161
+ // src/components/AdBanner.tsx
162
+ var import_jsx_runtime = require("react/jsx-runtime");
163
+ function AdBanner({
164
+ ad,
165
+ theme = "light",
166
+ size = "medium",
167
+ className,
168
+ style,
169
+ textStyle,
170
+ textClassName,
171
+ showLabel = true,
172
+ labelText = "Sponsored",
173
+ labelStyle,
174
+ onClick,
175
+ onImpression,
176
+ onClickTracked,
177
+ fallback = null,
178
+ disableImpressionTracking = false,
179
+ openInNewTab = true,
180
+ borderRadius,
181
+ backgroundColor,
182
+ textColor,
183
+ accentColor
184
+ }) {
185
+ const [isHovered, setIsHovered] = (0, import_react2.useState)(false);
186
+ const { handleClick } = useAdTracking({
187
+ ad,
188
+ disableImpressionTracking,
189
+ onImpression,
190
+ onClickTracked
191
+ });
192
+ if (!ad) {
193
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback });
194
+ }
195
+ const containerStyles = getAdBannerStyles(theme, size, {
196
+ backgroundColor,
197
+ textColor,
198
+ borderRadius,
199
+ style
200
+ });
201
+ if (isHovered && theme !== "minimal") {
202
+ containerStyles.transform = "translateY(-1px)";
203
+ containerStyles.boxShadow = theme === "dark" ? "0 4px 12px rgba(0, 0, 0, 0.4)" : theme === "branded" ? "0 4px 16px rgba(99, 102, 241, 0.4)" : "0 4px 12px rgba(0, 0, 0, 0.12)";
204
+ }
205
+ const labelStyles = {
206
+ ...baseLabelStyle,
207
+ color: accentColor || (theme === "branded" ? "rgba(255,255,255,0.8)" : void 0),
208
+ ...labelStyle
209
+ };
210
+ const textStyles = {
211
+ margin: 0,
212
+ ...textStyle
213
+ };
214
+ const handleClickInternal = (e) => {
215
+ handleClick();
216
+ onClick?.();
217
+ if (!ad.clickUrl) {
218
+ e.preventDefault();
219
+ }
220
+ };
221
+ const linkProps = ad.clickUrl ? {
222
+ href: ad.clickUrl,
223
+ target: openInNewTab ? "_blank" : void 0,
224
+ rel: openInNewTab ? "noopener noreferrer sponsored" : "sponsored"
225
+ } : {};
226
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
227
+ "a",
228
+ {
229
+ ...linkProps,
230
+ className,
231
+ style: containerStyles,
232
+ onClick: handleClickInternal,
233
+ onMouseEnter: () => setIsHovered(true),
234
+ onMouseLeave: () => setIsHovered(false),
235
+ "data-gravity-ad": true,
236
+ children: [
237
+ showLabel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: labelStyles, children: labelText }),
238
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: textClassName, style: textStyles, children: ad.adText })
239
+ ]
240
+ }
241
+ );
242
+ }
243
+ AdBanner.displayName = "GravityAdBanner";
244
+
245
+ // src/components/AdText.tsx
246
+ var import_jsx_runtime2 = require("react/jsx-runtime");
247
+ function AdText({
248
+ ad,
249
+ className,
250
+ style,
251
+ onClick,
252
+ onImpression,
253
+ onClickTracked,
254
+ fallback = null,
255
+ disableImpressionTracking = false,
256
+ openInNewTab = true
257
+ }) {
258
+ const { handleClick } = useAdTracking({
259
+ ad,
260
+ disableImpressionTracking,
261
+ onImpression,
262
+ onClickTracked
263
+ });
264
+ if (!ad) {
265
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
266
+ }
267
+ const handleClickInternal = (e) => {
268
+ handleClick();
269
+ onClick?.();
270
+ if (!ad.clickUrl) {
271
+ e.preventDefault();
272
+ }
273
+ };
274
+ const baseStyle = {
275
+ textDecoration: "none",
276
+ color: "inherit",
277
+ cursor: ad.clickUrl ? "pointer" : "default",
278
+ ...style
279
+ };
280
+ if (ad.clickUrl) {
281
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
282
+ "a",
283
+ {
284
+ href: ad.clickUrl,
285
+ target: openInNewTab ? "_blank" : void 0,
286
+ rel: openInNewTab ? "noopener noreferrer sponsored" : "sponsored",
287
+ className,
288
+ style: baseStyle,
289
+ onClick: handleClickInternal,
290
+ "data-gravity-ad": true,
291
+ children: ad.adText
292
+ }
293
+ );
294
+ }
295
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className, style: baseStyle, "data-gravity-ad": true, children: ad.adText });
296
+ }
297
+ AdText.displayName = "GravityAdText";
298
+ // Annotate the CommonJS export names for ESM import in node:
299
+ 0 && (module.exports = {
300
+ AdBanner,
301
+ AdText,
302
+ useAdTracking
303
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,274 @@
1
+ // src/components/AdBanner.tsx
2
+ import { useState } from "react";
3
+
4
+ // src/hooks/useAdTracking.ts
5
+ import { useEffect, useRef, useCallback } from "react";
6
+ function useAdTracking({
7
+ ad,
8
+ disableImpressionTracking = false,
9
+ onImpression,
10
+ onClickTracked
11
+ }) {
12
+ const impressionTracked = useRef(false);
13
+ useEffect(() => {
14
+ if (!ad || !ad.impUrl || disableImpressionTracking || impressionTracked.current) {
15
+ return;
16
+ }
17
+ const trackImpression = async () => {
18
+ try {
19
+ const img = new Image();
20
+ img.src = ad.impUrl;
21
+ impressionTracked.current = true;
22
+ onImpression?.();
23
+ } catch (error) {
24
+ console.error("[Gravity] Failed to track impression:", error);
25
+ }
26
+ };
27
+ trackImpression();
28
+ }, [ad, disableImpressionTracking, onImpression]);
29
+ useEffect(() => {
30
+ impressionTracked.current = false;
31
+ }, [ad?.impUrl]);
32
+ const handleClick = useCallback(() => {
33
+ if (!ad?.clickUrl) return;
34
+ onClickTracked?.();
35
+ }, [ad?.clickUrl, onClickTracked]);
36
+ return {
37
+ handleClick,
38
+ impressionTracked: impressionTracked.current
39
+ };
40
+ }
41
+
42
+ // src/styles.ts
43
+ var baseContainerStyle = {
44
+ display: "block",
45
+ textDecoration: "none",
46
+ cursor: "pointer",
47
+ transition: "all 0.2s ease",
48
+ boxSizing: "border-box",
49
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
50
+ };
51
+ var themeStyles = {
52
+ light: {
53
+ backgroundColor: "#ffffff",
54
+ color: "#1a1a1a",
55
+ border: "1px solid #e5e5e5",
56
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.08)"
57
+ },
58
+ dark: {
59
+ backgroundColor: "#1a1a1a",
60
+ color: "#f5f5f5",
61
+ border: "1px solid #333333",
62
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.3)"
63
+ },
64
+ minimal: {
65
+ backgroundColor: "transparent",
66
+ color: "inherit",
67
+ border: "none",
68
+ boxShadow: "none"
69
+ },
70
+ branded: {
71
+ backgroundColor: "#6366f1",
72
+ color: "#ffffff",
73
+ border: "none",
74
+ boxShadow: "0 2px 8px rgba(99, 102, 241, 0.3)"
75
+ }
76
+ };
77
+ var sizeStyles = {
78
+ small: {
79
+ padding: "8px 12px",
80
+ fontSize: "13px",
81
+ lineHeight: "1.4",
82
+ borderRadius: "6px"
83
+ },
84
+ medium: {
85
+ padding: "12px 16px",
86
+ fontSize: "14px",
87
+ lineHeight: "1.5",
88
+ borderRadius: "8px"
89
+ },
90
+ large: {
91
+ padding: "16px 20px",
92
+ fontSize: "16px",
93
+ lineHeight: "1.6",
94
+ borderRadius: "10px"
95
+ },
96
+ responsive: {
97
+ padding: "clamp(8px, 2vw, 16px) clamp(12px, 3vw, 20px)",
98
+ fontSize: "clamp(13px, 1.5vw, 16px)",
99
+ lineHeight: "1.5",
100
+ borderRadius: "clamp(6px, 1vw, 10px)"
101
+ }
102
+ };
103
+ var baseLabelStyle = {
104
+ fontSize: "10px",
105
+ fontWeight: 600,
106
+ textTransform: "uppercase",
107
+ letterSpacing: "0.5px",
108
+ opacity: 0.7,
109
+ marginBottom: "4px",
110
+ display: "block"
111
+ };
112
+ function getAdBannerStyles(theme, size, customStyles) {
113
+ const combined = {
114
+ ...baseContainerStyle,
115
+ ...themeStyles[theme],
116
+ ...sizeStyles[size]
117
+ };
118
+ if (customStyles?.backgroundColor) {
119
+ combined.backgroundColor = customStyles.backgroundColor;
120
+ }
121
+ if (customStyles?.textColor) {
122
+ combined.color = customStyles.textColor;
123
+ }
124
+ if (customStyles?.borderRadius !== void 0) {
125
+ combined.borderRadius = customStyles.borderRadius;
126
+ }
127
+ if (customStyles?.style) {
128
+ Object.assign(combined, customStyles.style);
129
+ }
130
+ return combined;
131
+ }
132
+
133
+ // src/components/AdBanner.tsx
134
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
135
+ function AdBanner({
136
+ ad,
137
+ theme = "light",
138
+ size = "medium",
139
+ className,
140
+ style,
141
+ textStyle,
142
+ textClassName,
143
+ showLabel = true,
144
+ labelText = "Sponsored",
145
+ labelStyle,
146
+ onClick,
147
+ onImpression,
148
+ onClickTracked,
149
+ fallback = null,
150
+ disableImpressionTracking = false,
151
+ openInNewTab = true,
152
+ borderRadius,
153
+ backgroundColor,
154
+ textColor,
155
+ accentColor
156
+ }) {
157
+ const [isHovered, setIsHovered] = useState(false);
158
+ const { handleClick } = useAdTracking({
159
+ ad,
160
+ disableImpressionTracking,
161
+ onImpression,
162
+ onClickTracked
163
+ });
164
+ if (!ad) {
165
+ return /* @__PURE__ */ jsx(Fragment, { children: fallback });
166
+ }
167
+ const containerStyles = getAdBannerStyles(theme, size, {
168
+ backgroundColor,
169
+ textColor,
170
+ borderRadius,
171
+ style
172
+ });
173
+ if (isHovered && theme !== "minimal") {
174
+ containerStyles.transform = "translateY(-1px)";
175
+ containerStyles.boxShadow = theme === "dark" ? "0 4px 12px rgba(0, 0, 0, 0.4)" : theme === "branded" ? "0 4px 16px rgba(99, 102, 241, 0.4)" : "0 4px 12px rgba(0, 0, 0, 0.12)";
176
+ }
177
+ const labelStyles = {
178
+ ...baseLabelStyle,
179
+ color: accentColor || (theme === "branded" ? "rgba(255,255,255,0.8)" : void 0),
180
+ ...labelStyle
181
+ };
182
+ const textStyles = {
183
+ margin: 0,
184
+ ...textStyle
185
+ };
186
+ const handleClickInternal = (e) => {
187
+ handleClick();
188
+ onClick?.();
189
+ if (!ad.clickUrl) {
190
+ e.preventDefault();
191
+ }
192
+ };
193
+ const linkProps = ad.clickUrl ? {
194
+ href: ad.clickUrl,
195
+ target: openInNewTab ? "_blank" : void 0,
196
+ rel: openInNewTab ? "noopener noreferrer sponsored" : "sponsored"
197
+ } : {};
198
+ return /* @__PURE__ */ jsxs(
199
+ "a",
200
+ {
201
+ ...linkProps,
202
+ className,
203
+ style: containerStyles,
204
+ onClick: handleClickInternal,
205
+ onMouseEnter: () => setIsHovered(true),
206
+ onMouseLeave: () => setIsHovered(false),
207
+ "data-gravity-ad": true,
208
+ children: [
209
+ showLabel && /* @__PURE__ */ jsx("span", { style: labelStyles, children: labelText }),
210
+ /* @__PURE__ */ jsx("p", { className: textClassName, style: textStyles, children: ad.adText })
211
+ ]
212
+ }
213
+ );
214
+ }
215
+ AdBanner.displayName = "GravityAdBanner";
216
+
217
+ // src/components/AdText.tsx
218
+ import { Fragment as Fragment2, jsx as jsx2 } from "react/jsx-runtime";
219
+ function AdText({
220
+ ad,
221
+ className,
222
+ style,
223
+ onClick,
224
+ onImpression,
225
+ onClickTracked,
226
+ fallback = null,
227
+ disableImpressionTracking = false,
228
+ openInNewTab = true
229
+ }) {
230
+ const { handleClick } = useAdTracking({
231
+ ad,
232
+ disableImpressionTracking,
233
+ onImpression,
234
+ onClickTracked
235
+ });
236
+ if (!ad) {
237
+ return /* @__PURE__ */ jsx2(Fragment2, { children: fallback });
238
+ }
239
+ const handleClickInternal = (e) => {
240
+ handleClick();
241
+ onClick?.();
242
+ if (!ad.clickUrl) {
243
+ e.preventDefault();
244
+ }
245
+ };
246
+ const baseStyle = {
247
+ textDecoration: "none",
248
+ color: "inherit",
249
+ cursor: ad.clickUrl ? "pointer" : "default",
250
+ ...style
251
+ };
252
+ if (ad.clickUrl) {
253
+ return /* @__PURE__ */ jsx2(
254
+ "a",
255
+ {
256
+ href: ad.clickUrl,
257
+ target: openInNewTab ? "_blank" : void 0,
258
+ rel: openInNewTab ? "noopener noreferrer sponsored" : "sponsored",
259
+ className,
260
+ style: baseStyle,
261
+ onClick: handleClickInternal,
262
+ "data-gravity-ad": true,
263
+ children: ad.adText
264
+ }
265
+ );
266
+ }
267
+ return /* @__PURE__ */ jsx2("span", { className, style: baseStyle, "data-gravity-ad": true, children: ad.adText });
268
+ }
269
+ AdText.displayName = "GravityAdText";
270
+ export {
271
+ AdBanner,
272
+ AdText,
273
+ useAdTracking
274
+ };
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@gravity-ai/react",
3
+ "version": "0.1.0",
4
+ "description": "React components for rendering Gravity AI advertisements",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "require": "./dist/index.js",
15
+ "import": "./dist/index.mjs"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format cjs,esm --dts --external react",
20
+ "clean": "rm -rf dist",
21
+ "dev": "tsup src/index.ts --format cjs,esm --watch --dts --external react",
22
+ "lint": "tsc --noEmit",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "prepublishOnly": "npm run clean && npm run build",
26
+ "version:patch": "npm version patch",
27
+ "version:minor": "npm version minor",
28
+ "version:major": "npm version major",
29
+ "publish:patch": "npm run version:patch && npm publish",
30
+ "publish:minor": "npm run version:minor && npm publish",
31
+ "publish:major": "npm run version:major && npm publish"
32
+ },
33
+ "keywords": [
34
+ "gravity",
35
+ "advertising",
36
+ "react",
37
+ "ads",
38
+ "typescript",
39
+ "contextual-advertising",
40
+ "ai-ads",
41
+ "ad-component"
42
+ ],
43
+ "author": "Gravity Team",
44
+ "license": "MIT",
45
+ "homepage": "https://github.com/Try-Gravity/gravity-js#readme",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/Try-Gravity/gravity-js.git",
49
+ "directory": "packages/react"
50
+ },
51
+ "bugs": {
52
+ "url": "https://github.com/Try-Gravity/gravity-js/issues"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ },
57
+ "engines": {
58
+ "node": ">=18.0.0"
59
+ },
60
+ "peerDependencies": {
61
+ "react": ">=17.0.0"
62
+ },
63
+ "devDependencies": {
64
+ "@testing-library/jest-dom": "^6.4.2",
65
+ "@testing-library/react": "^14.2.1",
66
+ "@types/react": "^18.2.48",
67
+ "jsdom": "^24.0.0",
68
+ "react": "^18.2.0",
69
+ "react-dom": "^18.2.0",
70
+ "tsup": "^8.0.1",
71
+ "typescript": "^5.3.3",
72
+ "vitest": "^1.2.1"
73
+ }
74
+ }
75
+