@qobo/banner 1.0.8 → 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.
package/README.md CHANGED
@@ -31,6 +31,39 @@ export default function App() {
31
31
  - **Error resilient**: Defaults to visible if API fails
32
32
  - **Layout shift prevention**: Initializes as visible before API response
33
33
  - **Responsive**: Works on all screen sizes
34
+ - **Navbar positioning**: Sets CSS variable `--qobo-banner-height` (0px when hidden, 50px when visible) for sticky navbar positioning
35
+ - **Sticky banner**: Uses sticky positioning so navbar can stick below it
36
+
37
+ ## Navbar Positioning
38
+
39
+ The banner automatically sets a CSS variable `--qobo-banner-height` on the document root that you can use for sticky navbar positioning:
40
+
41
+ - When banner is visible: `--qobo-banner-height` is `50px`
42
+ - When banner is hidden: `--qobo-banner-height` is `0px`
43
+
44
+ ### Using with Tailwind CSS
45
+
46
+ For your navbar component, use the CSS variable in your sticky positioning:
47
+
48
+ ```jsx
49
+ <nav className="sticky z-[60] bg-white border-b" style={{ top: 'var(--qobo-banner-height, 0px)' }}>
50
+ {/* Navbar content */}
51
+ </nav>
52
+ ```
53
+
54
+ Or using Tailwind's arbitrary values (requires Tailwind 3.1+):
55
+
56
+ ```jsx
57
+ <nav className="sticky z-[60] bg-white border-b" style={{ top: 'var(--qobo-banner-height)' }}>
58
+ {/* Navbar content */}
59
+ </nav>
60
+ ```
61
+
62
+ This ensures:
63
+ - When `showBanner` is `true`: Navbar sticks below the banner (at 50px from top)
64
+ - When `showBanner` is `false`: Navbar sticks to the top (at 0px from top)
65
+
66
+ **Important**: The banner uses `z-index: 50` and sticky positioning. Your navbar should use a higher z-index (e.g., `z-60` or `z-[60]`) to stay visible when scrolling.
34
67
 
35
68
  ## Environment Variables
36
69
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qobo/banner",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Shared Qobo banner component for all generated projects",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,17 +1,19 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import React, { useState, useEffect } from "react";
2
2
 
3
3
  /**
4
4
  * QoboBanner Component - Shared across all Qobo-generated projects
5
- *
5
+ *
6
6
  * Displays a banner with "Made with ❤️ by qobo.dev"
7
7
  * Visibility controlled by backend API endpoint /api/show-banner
8
- *
8
+ *
9
9
  * The component automatically:
10
10
  * - Fetches banner visibility on mount
11
11
  * - Shows banner if showBanner: "true", hides if "false"
12
12
  * - Defaults to visible on error
13
13
  * - Links to https://qobo.dev in a new tab
14
14
  * - Has consistent styling across all projects
15
+ * - Sets CSS variable --qobo-banner-height for navbar positioning (0px when hidden, 50px when visible)
16
+ * - Banner uses sticky positioning so navbar can stick below it
15
17
  */
16
18
  export function QoboBanner({ apiKey, apiBaseUrl }) {
17
19
  const [isVisible, setIsVisible] = useState(false); // Start hidden until API responds
@@ -23,37 +25,52 @@ export function QoboBanner({ apiKey, apiBaseUrl }) {
23
25
  const fetchBannerVisibility = async () => {
24
26
  try {
25
27
  // Get API key from environment
26
- const API_KEY = apiKey || import.meta.env.VITE_API_KEY || import.meta.env.REACT_APP_API_KEY;
27
- const API_BASE_URL = apiBaseUrl || import.meta.env.VITE_API_BASE_URL || import.meta.env.REACT_APP_API_BASE_URL || 'https://api.qobo.dev';
28
+ const API_KEY =
29
+ apiKey ||
30
+ import.meta.env.VITE_API_KEY ||
31
+ import.meta.env.REACT_APP_API_KEY;
32
+ const API_BASE_URL =
33
+ apiBaseUrl ||
34
+ import.meta.env.VITE_API_BASE_URL ||
35
+ import.meta.env.REACT_APP_API_BASE_URL ||
36
+ "https://api.qobo.dev";
28
37
 
29
38
  if (!API_KEY) {
30
- console.warn('[QoboBanner] API key not set (pass apiKey prop or set VITE_API_KEY), defaulting to visible');
39
+ console.warn(
40
+ "[QoboBanner] API key not set (pass apiKey prop or set VITE_API_KEY), defaulting to visible"
41
+ );
31
42
  setIsLoading(false);
32
43
  return;
33
44
  }
34
45
 
35
46
  const response = await fetch(`${API_BASE_URL}/api/v1/show-banner`, {
36
- method: 'GET',
47
+ method: "GET",
37
48
  headers: {
38
- 'Content-Type': 'application/json',
39
- 'X-API-Key': API_KEY,
49
+ "Content-Type": "application/json",
50
+ "X-API-Key": API_KEY,
40
51
  },
41
- cache: 'no-store',
52
+ cache: "no-store",
42
53
  });
43
54
 
44
55
  if (!response.ok) {
45
- console.warn(`[QoboBanner] Failed to fetch banner visibility (${response.status}), defaulting to visible`);
56
+ console.warn(
57
+ `[QoboBanner] Failed to fetch banner visibility (${response.status}), defaulting to visible`
58
+ );
46
59
  setIsLoading(false);
47
60
  return;
48
61
  }
49
62
 
50
63
  const data = await response.json();
51
- const showBannerValue = data.showBanner ?? data.data?.showBanner ?? 'true';
52
- const showBanner = showBannerValue === 'true';
64
+ const showBannerValue =
65
+ data.showBanner ?? data.data?.showBanner ?? "true";
66
+ const showBanner = showBannerValue === "true";
53
67
  setIsVisible(showBanner);
54
68
  console.log(`[QoboBanner] Banner visibility: ${showBanner}`);
55
69
  } catch (error) {
56
- console.warn('[QoboBanner] Error fetching banner visibility, defaulting to visible:', error.message);
70
+ console.warn(
71
+ "[QoboBanner] Error fetching banner visibility, defaulting to visible:",
72
+ error.message
73
+ );
57
74
  // Default to visible on error
58
75
  setIsVisible(true);
59
76
  } finally {
@@ -64,63 +81,75 @@ export function QoboBanner({ apiKey, apiBaseUrl }) {
64
81
  fetchBannerVisibility();
65
82
  }, [apiKey, apiBaseUrl]);
66
83
 
84
+ // Set CSS variable for banner height (always set, even when hidden)
85
+ // Navbars can use: top: var(--qobo-banner-height, 0px) for sticky positioning
86
+ useEffect(() => {
87
+ const bannerHeight = isVisible && !isLoading ? BANNER_HEIGHT : 0;
88
+ document.documentElement.style.setProperty(
89
+ "--qobo-banner-height",
90
+ `${bannerHeight}px`
91
+ );
92
+
93
+ return () => {
94
+ // Cleanup: reset to 0 when component unmounts
95
+ document.documentElement.style.setProperty("--qobo-banner-height", "0px");
96
+ };
97
+ }, [isVisible, isLoading, BANNER_HEIGHT]);
98
+
67
99
  if (isLoading) return null;
68
100
  if (!isVisible) return null;
69
101
 
70
102
  return (
71
- <>
72
- <div aria-hidden="true" style={{ height: `${BANNER_HEIGHT}px`, width: '100%' }} />
73
- <div
74
- className="w-full bg-gradient-to-r from-cyan-400 to-blue-500 text-white py-2 px-4 text-center text-sm font-medium shadow-md"
103
+ <div
104
+ className="w-full bg-gradient-to-r from-cyan-400 to-blue-500 text-white py-2 px-4 text-center text-sm font-medium shadow-md"
105
+ style={{
106
+ position: "sticky",
107
+ top: 0,
108
+ left: 0,
109
+ right: 0,
110
+ zIndex: 50,
111
+ height: `${BANNER_HEIGHT}px`,
112
+ background: "linear-gradient(90deg, #FF7A2F 0%, #46C6B9 100%)",
113
+ color: "#F1F5F9",
114
+ padding: "12px 16px",
115
+ textAlign: "center",
116
+ fontSize: "0.95rem",
117
+ fontWeight: "600",
118
+ letterSpacing: "0.01em",
119
+ fontFamily: '"Inter", "Segoe UI", system-ui, -apple-system, sans-serif',
120
+ boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
121
+ transition: "all 0.3s ease",
122
+ display: "flex",
123
+ alignItems: "center",
124
+ justifyContent: "center",
125
+ }}
126
+ >
127
+ Made with{" "}
128
+ <span
129
+ style={{
130
+ color: "#FF1744",
131
+ display: "inline-block",
132
+ margin: "0 0.35rem",
133
+ }}
134
+ >
135
+ ❤️
136
+ </span>{" "}
137
+ by{" "}
138
+ <a
139
+ href="https://qobo.dev"
140
+ target="_blank"
141
+ rel="noopener noreferrer"
75
142
  style={{
76
- position: 'fixed',
77
- top: 0,
78
- left: 0,
79
- right: 0,
80
- zIndex: 9999,
81
- height: `${BANNER_HEIGHT}px`,
82
- background: 'linear-gradient(90deg, #FF7A2F 0%, #46C6B9 100%)',
83
- color: '#F1F5F9',
84
- padding: '12px 16px',
85
- textAlign: 'center',
86
- fontSize: '0.95rem',
87
- fontWeight: '600',
88
- letterSpacing: '0.01em',
89
- fontFamily: '"Inter", "Segoe UI", system-ui, -apple-system, sans-serif',
90
- boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
91
- transition: 'all 0.3s ease',
92
- display: 'flex',
93
- alignItems: 'center',
94
- justifyContent: 'center',
143
+ color: "#F1F5F9",
144
+ textDecoration: "underline",
145
+ fontWeight: "700",
146
+ cursor: "pointer",
95
147
  }}
148
+ className="hover:text-cyan-100 transition-colors ml-2"
96
149
  >
97
- Made with{' '}
98
- <span
99
- style={{
100
- color: '#FF1744',
101
- display: 'inline-block',
102
- margin: '0 0.35rem',
103
- }}
104
- >
105
- ❤️
106
- </span>{' '}
107
- by{' '}
108
- <a
109
- href="https://qobo.dev"
110
- target="_blank"
111
- rel="noopener noreferrer"
112
- style={{
113
- color: '#F1F5F9',
114
- textDecoration: 'underline',
115
- fontWeight: '700',
116
- cursor: 'pointer',
117
- }}
118
- className="hover:text-cyan-100 transition-colors"
119
- >
120
- qobo.dev
121
- </a>
122
- </div>
123
- </>
150
+ qobo.dev
151
+ </a>
152
+ </div>
124
153
  );
125
154
  }
126
155
 
package/src/index.js CHANGED
@@ -1 +1,2 @@
1
1
  export { QoboBanner, default } from './QoboBanner.jsx';
2
+ export { BannerProvider, useBannerVisibility } from './BannerContext.jsx';