@sanvika/ui 0.1.5 → 0.2.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/package.json +15 -29
- package/src/components/buttons/Button.jsx +97 -0
- package/src/components/buttons/Button.stories.jsx +110 -0
- package/src/components/buttons/FavoriteHeartButton.jsx +41 -0
- package/src/components/buttons/FavoriteHeartButton.stories.jsx +48 -0
- package/src/components/buttons/ScrollButton.jsx +36 -0
- package/src/components/buttons/ScrollButton.stories.jsx +21 -0
- package/src/components/buttons/ThreeDotButton.jsx +36 -0
- package/src/components/buttons/ThreeDotButton.stories.jsx +14 -0
- package/src/components/common/Container.jsx +38 -0
- package/src/components/common/Section.jsx +60 -0
- package/src/components/common/Section.stories.jsx +64 -0
- package/src/components/icons/BellIcon.jsx +10 -0
- package/src/components/icons/ChevronDown.jsx +20 -0
- package/src/components/icons/EmailIcon.jsx +9 -0
- package/src/components/icons/FaAngleDown.jsx +9 -0
- package/src/components/icons/FaAngleRight.jsx +9 -0
- package/src/components/icons/FaArrowDown.jsx +9 -0
- package/src/components/icons/FaArrowLeft.jsx +9 -0
- package/src/components/icons/FaArrowUp.jsx +9 -0
- package/src/components/icons/FaBell.jsx +9 -0
- package/src/components/icons/FaBullhorn.jsx +10 -0
- package/src/components/icons/FaCalendarAlt.jsx +9 -0
- package/src/components/icons/FaCheck.jsx +9 -0
- package/src/components/icons/FaCheckCircle.jsx +9 -0
- package/src/components/icons/FaCheckDouble.jsx +9 -0
- package/src/components/icons/FaChevronDown.jsx +9 -0
- package/src/components/icons/FaChevronRight.jsx +9 -0
- package/src/components/icons/FaCircle.jsx +9 -0
- package/src/components/icons/FaComments.jsx +9 -0
- package/src/components/icons/FaEye.jsx +9 -0
- package/src/components/icons/FaEyeSlash.jsx +9 -0
- package/src/components/icons/FaHome.jsx +9 -0
- package/src/components/icons/FaKeyboard.jsx +9 -0
- package/src/components/icons/FaLocationArrow.jsx +9 -0
- package/src/components/icons/FaMapMarkerAlt.jsx +9 -0
- package/src/components/icons/FaMoon.jsx +9 -0
- package/src/components/icons/FaPaperPlane.jsx +9 -0
- package/src/components/icons/FaPaperclip.jsx +9 -0
- package/src/components/icons/FaSignInAlt.jsx +9 -0
- package/src/components/icons/FaSmile.jsx +9 -0
- package/src/components/icons/FaStar.jsx +9 -0
- package/src/components/icons/FaTag.jsx +9 -0
- package/src/components/icons/FaThumbsDown.jsx +9 -0
- package/src/components/icons/FaThumbsUp.jsx +9 -0
- package/src/components/icons/FaTrash.jsx +9 -0
- package/src/components/icons/FaUser.jsx +9 -0
- package/src/components/icons/FaUserCircle.jsx +9 -0
- package/src/components/icons/FacebookIcon.jsx +9 -0
- package/src/components/icons/HalfMoonIcon.jsx +18 -0
- package/src/components/icons/HeartIcon.jsx +9 -0
- package/src/components/icons/InstagramIcon.jsx +9 -0
- package/src/components/icons/LinkedInIcon.jsx +9 -0
- package/src/components/icons/MapMarkerIcon.jsx +9 -0
- package/src/components/icons/MdWbSunny.jsx +9 -0
- package/src/components/icons/ReFreshIcon.jsx +49 -0
- package/src/components/icons/SearchIcon.jsx +20 -0
- package/src/components/icons/StarIcon.jsx +9 -0
- package/src/components/icons/TelegramIcon.jsx +9 -0
- package/src/components/icons/TwitterIcon.jsx +9 -0
- package/src/components/icons/UserIcon.jsx +9 -0
- package/src/components/icons/WhatsappIcon.jsx +9 -0
- package/src/components/icons/YoutubeIcon.jsx +9 -0
- package/src/components/icons/index.js +60 -0
- package/src/components/layout/Footer.jsx +53 -0
- package/src/components/layout/Footer.stories.jsx +28 -0
- package/src/components/layout/Navbar.jsx +50 -0
- package/src/components/layout/Navbar.stories.jsx +42 -0
- package/src/components/layout/SubNavbar.jsx +36 -0
- package/src/components/modals/Modal.jsx +74 -0
- package/src/components/modals/Modal.stories.jsx +93 -0
- package/src/components/progressBar/ProgressBar.jsx +113 -0
- package/src/components/progressBar/ProgressBar.stories.jsx +67 -0
- package/src/context/ThemeContext.jsx +91 -0
- package/src/index.js +33 -0
- package/src/layouts/Layout.jsx +24 -0
- package/src/server/index.js +159 -0
- package/README.md +0 -36
- package/dist/EmailIcon-DumDw7u2.js +0 -781
- package/dist/EmailIcon-ssF1iAVu.js +0 -782
- package/dist/icons/index.js +0 -4
- package/dist/index.js +0 -555
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
// src/context/ThemeContext.jsx
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useState,
|
|
8
|
+
useCallback,
|
|
9
|
+
} from "react";
|
|
10
|
+
|
|
11
|
+
const ThemeContext = createContext();
|
|
12
|
+
|
|
13
|
+
export const ThemeProvider = ({ children }) => {
|
|
14
|
+
const [mounted, setMounted] = useState(false);
|
|
15
|
+
const [theme, setTheme] = useState("light"); // SSR fallback
|
|
16
|
+
|
|
17
|
+
// Step 1: On mount, load theme from localStorage. Default = dark.
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
try {
|
|
20
|
+
const saved = localStorage.getItem("theme");
|
|
21
|
+
const resolved = saved ?? "dark"; // First visit = dark
|
|
22
|
+
setTheme(resolved);
|
|
23
|
+
document.documentElement.setAttribute("data-theme", resolved);
|
|
24
|
+
|
|
25
|
+
// Listen for OS-level theme changes only if user has not set a preference
|
|
26
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
27
|
+
const onSystemChange = (e) => {
|
|
28
|
+
if (!localStorage.getItem("theme")) {
|
|
29
|
+
const sys = e.matches ? "dark" : "light";
|
|
30
|
+
setTheme(sys);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
mq.addEventListener("change", onSystemChange);
|
|
34
|
+
setMounted(true);
|
|
35
|
+
return () => mq.removeEventListener("change", onSystemChange);
|
|
36
|
+
} catch {
|
|
37
|
+
setTheme("dark");
|
|
38
|
+
document.documentElement.setAttribute("data-theme", "dark");
|
|
39
|
+
setMounted(true);
|
|
40
|
+
}
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
// Step 2: Apply theme with smooth transition class
|
|
44
|
+
const applyTheme = useCallback((next) => {
|
|
45
|
+
const root = document.documentElement;
|
|
46
|
+
root.classList.add("theme-transition");
|
|
47
|
+
root.setAttribute("data-theme", next);
|
|
48
|
+
localStorage.setItem("theme", next);
|
|
49
|
+
const t = setTimeout(() => root.classList.remove("theme-transition"), 300);
|
|
50
|
+
return () => clearTimeout(t);
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
// Step 3: Apply whenever theme state changes (after mount)
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (mounted) applyTheme(theme);
|
|
56
|
+
}, [theme, mounted, applyTheme]);
|
|
57
|
+
|
|
58
|
+
// Step 4: Toggle
|
|
59
|
+
const toggleTheme = useCallback(() => {
|
|
60
|
+
setTheme((prev) => (prev === "light" ? "dark" : "light"));
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
// Step 5: Explicit setter (for admin/testing)
|
|
64
|
+
const setThemeExplicitly = useCallback((next) => {
|
|
65
|
+
if (next !== "light" && next !== "dark") return;
|
|
66
|
+
setTheme(next);
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
// React 19 direct context syntax
|
|
70
|
+
return (
|
|
71
|
+
<ThemeContext
|
|
72
|
+
value={{
|
|
73
|
+
theme,
|
|
74
|
+
toggleTheme,
|
|
75
|
+
setTheme: setThemeExplicitly,
|
|
76
|
+
isDarkMode: theme === "dark",
|
|
77
|
+
isThemeReady: mounted,
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{children}
|
|
81
|
+
</ThemeContext>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const useTheme = () => {
|
|
86
|
+
const ctx = useContext(ThemeContext);
|
|
87
|
+
if (!ctx) throw new Error("useTheme must be used inside ThemeProvider");
|
|
88
|
+
return ctx;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export { ThemeContext };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// src/index.js
|
|
2
|
+
// @sanvika/ui — main barrel export
|
|
3
|
+
// Import this in your project: import { Button, Modal, Navbar, ... } from "@sanvika/ui"
|
|
4
|
+
|
|
5
|
+
// ─── Layout ──────────────────────────────────────────────────────────────────
|
|
6
|
+
export { default as Navbar } from "./components/layout/Navbar.jsx";
|
|
7
|
+
export { default as SubNavbar } from "./components/layout/SubNavbar.jsx";
|
|
8
|
+
export { default as Footer } from "./components/layout/Footer.jsx";
|
|
9
|
+
|
|
10
|
+
// ─── Buttons ─────────────────────────────────────────────────────────────────
|
|
11
|
+
export { default as Button } from "./components/buttons/Button.jsx";
|
|
12
|
+
export { default as FavoriteHeartButton } from "./components/buttons/FavoriteHeartButton.jsx";
|
|
13
|
+
export { default as ScrollButton } from "./components/buttons/ScrollButton.jsx";
|
|
14
|
+
export { default as ThreeDotButton } from "./components/buttons/ThreeDotButton.jsx";
|
|
15
|
+
|
|
16
|
+
// ─── Common ──────────────────────────────────────────────────────────────────
|
|
17
|
+
export { default as Container } from "./components/common/Container.jsx";
|
|
18
|
+
export { default as Section } from "./components/common/Section.jsx";
|
|
19
|
+
|
|
20
|
+
// ─── Modals ──────────────────────────────────────────────────────────────────
|
|
21
|
+
export { default as Modal } from "./components/modals/Modal.jsx";
|
|
22
|
+
|
|
23
|
+
// ─── ProgressBar ─────────────────────────────────────────────────────────────
|
|
24
|
+
export { default as ProgressBar } from "./components/progressBar/ProgressBar.jsx";
|
|
25
|
+
|
|
26
|
+
// ─── Icons (re-exported from icons barrel) ───────────────────────────────────
|
|
27
|
+
export * from "./components/icons/index.js";
|
|
28
|
+
|
|
29
|
+
// ─── Theme Context ────────────────────────────────────────────────────────────
|
|
30
|
+
export { ThemeProvider, useTheme } from "./context/ThemeContext.jsx";
|
|
31
|
+
|
|
32
|
+
// ─── Page Layout ─────────────────────────────────────────────────────────────
|
|
33
|
+
export { default as Layout } from "./layouts/Layout.jsx";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/layouts/Layout.jsx
|
|
2
|
+
import Navbar from "../components/layout/Navbar";
|
|
3
|
+
import Footer from "../components/layout/Footer";
|
|
4
|
+
import styles from "../styles/components/layouts/Layout.module.css";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Root page layout — wraps every page with Navbar + Footer.
|
|
8
|
+
* The navbar is fixed; content area has a top margin to compensate.
|
|
9
|
+
*/
|
|
10
|
+
const Layout = ({ children }) => {
|
|
11
|
+
return (
|
|
12
|
+
<div className={styles.wrapper}>
|
|
13
|
+
<Navbar />
|
|
14
|
+
<div className={styles.content}>
|
|
15
|
+
<main className={styles.main}>{children}</main>
|
|
16
|
+
</div>
|
|
17
|
+
<div className={styles.footerWrapper}>
|
|
18
|
+
<Footer />
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default Layout;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// @sanvika/ui/server v0.2.0
|
|
2
|
+
// HTTP client for ui.sanvikaproduction.com — cloud-driven Navbar configs and Theme presets.
|
|
3
|
+
// Env vars: UI_URL, UI_CLIENT_SECRET (or pass via constructor).
|
|
4
|
+
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 8000;
|
|
6
|
+
|
|
7
|
+
function _readEnv(key) {
|
|
8
|
+
try {
|
|
9
|
+
return (typeof process !== "undefined" && process.env?.[key]) || null;
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class SanvikaUI {
|
|
16
|
+
#url;
|
|
17
|
+
#secret;
|
|
18
|
+
|
|
19
|
+
constructor({ url, secret } = {}) {
|
|
20
|
+
const finalUrl = url || _readEnv("UI_URL");
|
|
21
|
+
const finalSecret = secret || _readEnv("UI_CLIENT_SECRET");
|
|
22
|
+
if (!finalUrl || !finalSecret) {
|
|
23
|
+
throw new Error("@sanvika/ui/server: UI_URL and UI_CLIENT_SECRET are required");
|
|
24
|
+
}
|
|
25
|
+
this.#url = finalUrl.replace(/\/$/, "");
|
|
26
|
+
this.#secret = finalSecret;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get baseUrl() {
|
|
30
|
+
return this.#url;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async #request(path, { method = "POST", body, query } = {}) {
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
|
|
36
|
+
|
|
37
|
+
const qs = query ? "?" + new URLSearchParams(query).toString() : "";
|
|
38
|
+
const init = {
|
|
39
|
+
method,
|
|
40
|
+
headers: { "x-client-secret": this.#secret },
|
|
41
|
+
signal: controller.signal,
|
|
42
|
+
};
|
|
43
|
+
if (body !== undefined) {
|
|
44
|
+
init.headers["Content-Type"] = "application/json";
|
|
45
|
+
init.body = JSON.stringify(body);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetch(`${this.#url}${path}${qs}`, init);
|
|
50
|
+
const json = await res.json().catch(() => ({}));
|
|
51
|
+
if (!res.ok || !json.success) {
|
|
52
|
+
return { success: false, status: res.status, error: json?.error || `HTTP_${res.status}` };
|
|
53
|
+
}
|
|
54
|
+
return { success: true, ...json };
|
|
55
|
+
} catch (err) {
|
|
56
|
+
return { success: false, error: err.name === "AbortError" ? "TIMEOUT" : err.message };
|
|
57
|
+
} finally {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Fetch latest published navbar config (default name = "default"). */
|
|
63
|
+
async getNavbarConfig({ name = "default", version } = {}) {
|
|
64
|
+
const query = {};
|
|
65
|
+
if (version !== undefined) query.version = String(version);
|
|
66
|
+
return this.#request(`/api/v1/navbar/${encodeURIComponent(name)}`, {
|
|
67
|
+
method: "GET",
|
|
68
|
+
query: Object.keys(query).length ? query : undefined,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Save (creates new version) a navbar config. */
|
|
73
|
+
async saveNavbarConfig({ name = "default", items, cta, logoUrl, activeThemePreset, status = "published" } = {}) {
|
|
74
|
+
if (!Array.isArray(items)) throw new Error("saveNavbarConfig: items[] is required");
|
|
75
|
+
return this.#request("/api/v1/navbar", {
|
|
76
|
+
body: { name, items, cta, logoUrl, activeThemePreset, status },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** List latest navbar configs across names. */
|
|
81
|
+
async listNavbarConfigs() {
|
|
82
|
+
return this.#request("/api/v1/navbar", { method: "GET" });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Soft-delete all versions of a navbar config name. */
|
|
86
|
+
async deleteNavbarConfig(name) {
|
|
87
|
+
if (!name) throw new Error("deleteNavbarConfig: name is required");
|
|
88
|
+
return this.#request(`/api/v1/navbar/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Fetch a single theme preset by name. */
|
|
92
|
+
async getThemePreset({ name } = {}) {
|
|
93
|
+
if (!name) throw new Error("getThemePreset: name is required");
|
|
94
|
+
return this.#request(`/api/v1/theme/${encodeURIComponent(name)}`, { method: "GET" });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Upsert a theme preset by name. */
|
|
98
|
+
async saveThemePreset({ name, mode = "light", palette = {}, fontFamily, radius, extras, status = "published" } = {}) {
|
|
99
|
+
if (!name) throw new Error("saveThemePreset: name is required");
|
|
100
|
+
return this.#request("/api/v1/theme", {
|
|
101
|
+
body: { name, mode, palette, fontFamily, radius, extras, status },
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** List theme presets. */
|
|
106
|
+
async listThemePresets() {
|
|
107
|
+
return this.#request("/api/v1/theme", { method: "GET" });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Soft-delete a theme preset. */
|
|
111
|
+
async deleteThemePreset(name) {
|
|
112
|
+
if (!name) throw new Error("deleteThemePreset: name is required");
|
|
113
|
+
return this.#request(`/api/v1/theme/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function createUiClient(opts = {}) {
|
|
118
|
+
return new SanvikaUI(opts);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let _instance = null;
|
|
122
|
+
function _getInstance() {
|
|
123
|
+
if (!_instance) _instance = new SanvikaUI();
|
|
124
|
+
return _instance;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function getNavbarConfig(opts = {}) {
|
|
128
|
+
return _getInstance().getNavbarConfig(opts);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function saveNavbarConfig(input) {
|
|
132
|
+
return _getInstance().saveNavbarConfig(input);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function listNavbarConfigs() {
|
|
136
|
+
return _getInstance().listNavbarConfigs();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function deleteNavbarConfig(name) {
|
|
140
|
+
return _getInstance().deleteNavbarConfig(name);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function getThemePreset(opts) {
|
|
144
|
+
return _getInstance().getThemePreset(opts);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function saveThemePreset(input) {
|
|
148
|
+
return _getInstance().saveThemePreset(input);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function listThemePresets() {
|
|
152
|
+
return _getInstance().listThemePresets();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function deleteThemePreset(name) {
|
|
156
|
+
return _getInstance().deleteThemePreset(name);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export default createUiClient;
|
package/README.md
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
|
2
|
-
|
|
3
|
-
## Getting Started
|
|
4
|
-
|
|
5
|
-
First, run the development server:
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm run dev
|
|
9
|
-
# or
|
|
10
|
-
yarn dev
|
|
11
|
-
# or
|
|
12
|
-
pnpm dev
|
|
13
|
-
# or
|
|
14
|
-
bun dev
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
18
|
-
|
|
19
|
-
You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
|
|
20
|
-
|
|
21
|
-
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
|
22
|
-
|
|
23
|
-
## Learn More
|
|
24
|
-
|
|
25
|
-
To learn more about Next.js, take a look at the following resources:
|
|
26
|
-
|
|
27
|
-
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
28
|
-
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
29
|
-
|
|
30
|
-
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
31
|
-
|
|
32
|
-
## Deploy on Vercel
|
|
33
|
-
|
|
34
|
-
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
35
|
-
|
|
36
|
-
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|