@ankit.blumox/theme-sdk 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 +23 -0
- package/dist/index.d.mts +77 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +114 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +106 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +42 -0
- package/templates/starter/Layout.tsx +43 -0
- package/templates/starter/README.md +12 -0
- package/templates/starter/styles.css +54 -0
- package/templates/starter/theme.config.ts +12 -0
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @blumox/theme-sdk
|
|
2
|
+
|
|
3
|
+
Official TypeScript SDK for building custom themes for the Blumox multi-tenant SaaS platform.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@blumox/theme-sdk)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Build custom themes for the Blumox platform using a revolutionary "liquid architecture" where themes control 100% of the layout and design while the platform provides data [file:1]. Create once, deploy to thousands of tenants across the marketplace.
|
|
11
|
+
|
|
12
|
+
## What is This?
|
|
13
|
+
|
|
14
|
+
The Blumox platform uses a unique architecture where:
|
|
15
|
+
- **Platform = Data Provider** - Supplies navigation, user info, content via props
|
|
16
|
+
- **Your Theme = Visual Container** - Controls all layout, styling, and UX
|
|
17
|
+
|
|
18
|
+
Think of it like water and containers: the platform is shapeless water, your theme is the container that gives it form. Same data, infinite design possibilities.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @blumox/theme-sdk
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
interface NavigationItem {
|
|
4
|
+
label: string;
|
|
5
|
+
path: string;
|
|
6
|
+
icon?: string;
|
|
7
|
+
badge?: string | number;
|
|
8
|
+
children?: NavigationItem[];
|
|
9
|
+
}
|
|
10
|
+
interface NavigationData {
|
|
11
|
+
items: NavigationItem[];
|
|
12
|
+
logo?: string;
|
|
13
|
+
brandName: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type UserRole = 'admin' | 'user' | 'super_admin';
|
|
17
|
+
interface UserData {
|
|
18
|
+
userId: string;
|
|
19
|
+
name: string;
|
|
20
|
+
email: string;
|
|
21
|
+
role: UserRole;
|
|
22
|
+
avatar?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface TenantConfig {
|
|
26
|
+
tenantId: string;
|
|
27
|
+
tenantName: string;
|
|
28
|
+
logo?: string;
|
|
29
|
+
primaryColor?: string;
|
|
30
|
+
currency?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ThemeLayoutProps {
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
navigation: NavigationData;
|
|
36
|
+
user: UserData;
|
|
37
|
+
tenant: TenantConfig;
|
|
38
|
+
}
|
|
39
|
+
interface ThemeManifest {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
version: string;
|
|
43
|
+
author: string;
|
|
44
|
+
description: string;
|
|
45
|
+
screenshots: string[];
|
|
46
|
+
category: 'modern' | 'classic' | 'minimal' | 'creative';
|
|
47
|
+
pricing: 'free' | 'paid';
|
|
48
|
+
price?: number;
|
|
49
|
+
tags?: string[];
|
|
50
|
+
}
|
|
51
|
+
interface ThemeMetadata {
|
|
52
|
+
id: string;
|
|
53
|
+
name: string;
|
|
54
|
+
author: string;
|
|
55
|
+
version: string;
|
|
56
|
+
description: string;
|
|
57
|
+
preview: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
declare function cn(...classes: (string | undefined | null | false | 0)[]): string;
|
|
61
|
+
declare function getAvatarColor(userId: string): string;
|
|
62
|
+
|
|
63
|
+
declare function getUserInitials(name: string): string;
|
|
64
|
+
|
|
65
|
+
declare function formatDate(date: Date | string, format?: 'short' | 'long' | 'relative'): string;
|
|
66
|
+
|
|
67
|
+
declare function isActiveRoute(currentPath: string, itemPath: string): boolean;
|
|
68
|
+
|
|
69
|
+
interface ValidationResult {
|
|
70
|
+
valid: boolean;
|
|
71
|
+
errors: string[];
|
|
72
|
+
warnings: string[];
|
|
73
|
+
}
|
|
74
|
+
declare function validateThemeConfig(config: ThemeManifest): ValidationResult;
|
|
75
|
+
declare function isValidVersion(version: string): boolean;
|
|
76
|
+
|
|
77
|
+
export { type NavigationData, type NavigationItem, type TenantConfig, type ThemeLayoutProps, type ThemeManifest, type ThemeMetadata, type UserData, type UserRole, type ValidationResult, cn, formatDate, getAvatarColor, getUserInitials, isActiveRoute, isValidVersion, validateThemeConfig };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
interface NavigationItem {
|
|
4
|
+
label: string;
|
|
5
|
+
path: string;
|
|
6
|
+
icon?: string;
|
|
7
|
+
badge?: string | number;
|
|
8
|
+
children?: NavigationItem[];
|
|
9
|
+
}
|
|
10
|
+
interface NavigationData {
|
|
11
|
+
items: NavigationItem[];
|
|
12
|
+
logo?: string;
|
|
13
|
+
brandName: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type UserRole = 'admin' | 'user' | 'super_admin';
|
|
17
|
+
interface UserData {
|
|
18
|
+
userId: string;
|
|
19
|
+
name: string;
|
|
20
|
+
email: string;
|
|
21
|
+
role: UserRole;
|
|
22
|
+
avatar?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface TenantConfig {
|
|
26
|
+
tenantId: string;
|
|
27
|
+
tenantName: string;
|
|
28
|
+
logo?: string;
|
|
29
|
+
primaryColor?: string;
|
|
30
|
+
currency?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ThemeLayoutProps {
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
navigation: NavigationData;
|
|
36
|
+
user: UserData;
|
|
37
|
+
tenant: TenantConfig;
|
|
38
|
+
}
|
|
39
|
+
interface ThemeManifest {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
version: string;
|
|
43
|
+
author: string;
|
|
44
|
+
description: string;
|
|
45
|
+
screenshots: string[];
|
|
46
|
+
category: 'modern' | 'classic' | 'minimal' | 'creative';
|
|
47
|
+
pricing: 'free' | 'paid';
|
|
48
|
+
price?: number;
|
|
49
|
+
tags?: string[];
|
|
50
|
+
}
|
|
51
|
+
interface ThemeMetadata {
|
|
52
|
+
id: string;
|
|
53
|
+
name: string;
|
|
54
|
+
author: string;
|
|
55
|
+
version: string;
|
|
56
|
+
description: string;
|
|
57
|
+
preview: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
declare function cn(...classes: (string | undefined | null | false | 0)[]): string;
|
|
61
|
+
declare function getAvatarColor(userId: string): string;
|
|
62
|
+
|
|
63
|
+
declare function getUserInitials(name: string): string;
|
|
64
|
+
|
|
65
|
+
declare function formatDate(date: Date | string, format?: 'short' | 'long' | 'relative'): string;
|
|
66
|
+
|
|
67
|
+
declare function isActiveRoute(currentPath: string, itemPath: string): boolean;
|
|
68
|
+
|
|
69
|
+
interface ValidationResult {
|
|
70
|
+
valid: boolean;
|
|
71
|
+
errors: string[];
|
|
72
|
+
warnings: string[];
|
|
73
|
+
}
|
|
74
|
+
declare function validateThemeConfig(config: ThemeManifest): ValidationResult;
|
|
75
|
+
declare function isValidVersion(version: string): boolean;
|
|
76
|
+
|
|
77
|
+
export { type NavigationData, type NavigationItem, type TenantConfig, type ThemeLayoutProps, type ThemeManifest, type ThemeMetadata, type UserData, type UserRole, type ValidationResult, cn, formatDate, getAvatarColor, getUserInitials, isActiveRoute, isValidVersion, validateThemeConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/utils/styling.ts
|
|
4
|
+
function cn(...classes) {
|
|
5
|
+
return classes.filter(Boolean).join(" ");
|
|
6
|
+
}
|
|
7
|
+
function getAvatarColor(userId) {
|
|
8
|
+
const colors = [
|
|
9
|
+
"#4F46E5",
|
|
10
|
+
"#7C3AED",
|
|
11
|
+
"#DB2777",
|
|
12
|
+
"#DC2626",
|
|
13
|
+
"#EA580C",
|
|
14
|
+
"#CA8A04",
|
|
15
|
+
"#16A34A",
|
|
16
|
+
"#0891B2",
|
|
17
|
+
"#2563EB"
|
|
18
|
+
];
|
|
19
|
+
let hash = 0;
|
|
20
|
+
for (let i = 0; i < userId.length; i++) {
|
|
21
|
+
hash = userId.charCodeAt(i) + ((hash << 5) - hash);
|
|
22
|
+
}
|
|
23
|
+
return colors[Math.abs(hash) % colors.length];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/utils/user.ts
|
|
27
|
+
function getUserInitials(name) {
|
|
28
|
+
if (!name || typeof name !== "string") return "?";
|
|
29
|
+
return name.trim().split(/\s+/).filter(Boolean).map((word) => word[0]).join("").toUpperCase().slice(0, 2);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/utils/formatting.ts
|
|
33
|
+
function formatDate(date, format = "short") {
|
|
34
|
+
const dateObj = typeof date === "string" ? new Date(date) : date;
|
|
35
|
+
if (isNaN(dateObj.getTime())) return "Invalid date";
|
|
36
|
+
switch (format) {
|
|
37
|
+
case "short":
|
|
38
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
39
|
+
month: "short",
|
|
40
|
+
day: "numeric",
|
|
41
|
+
year: "numeric"
|
|
42
|
+
}).format(dateObj);
|
|
43
|
+
case "long":
|
|
44
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
45
|
+
month: "long",
|
|
46
|
+
day: "numeric",
|
|
47
|
+
year: "numeric",
|
|
48
|
+
hour: "numeric",
|
|
49
|
+
minute: "2-digit"
|
|
50
|
+
}).format(dateObj);
|
|
51
|
+
case "relative":
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
const diff = now - dateObj.getTime();
|
|
54
|
+
const seconds = Math.floor(diff / 1e3);
|
|
55
|
+
const minutes = Math.floor(seconds / 60);
|
|
56
|
+
const hours = Math.floor(minutes / 60);
|
|
57
|
+
const days = Math.floor(hours / 24);
|
|
58
|
+
if (seconds < 60) return "Just now";
|
|
59
|
+
if (minutes < 60) return `${minutes} minute${minutes > 1 ? "s" : ""} ago`;
|
|
60
|
+
if (hours < 24) return `${hours} hour${hours > 1 ? "s" : ""} ago`;
|
|
61
|
+
if (days < 7) return `${days} day${days > 1 ? "s" : ""} ago`;
|
|
62
|
+
return formatDate(dateObj, "short");
|
|
63
|
+
default:
|
|
64
|
+
return dateObj.toLocaleDateString();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/utils/navigation.ts
|
|
69
|
+
function isActiveRoute(currentPath, itemPath) {
|
|
70
|
+
if (!currentPath || !itemPath) return false;
|
|
71
|
+
if (currentPath === itemPath) return true;
|
|
72
|
+
if (itemPath !== "/" && currentPath.startsWith(itemPath)) return true;
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/utils/validation.ts
|
|
77
|
+
function validateThemeConfig(config) {
|
|
78
|
+
const errors = [];
|
|
79
|
+
const warnings = [];
|
|
80
|
+
if (!config.id) errors.push("Theme ID is required");
|
|
81
|
+
if (!config.name) errors.push("Theme name is required");
|
|
82
|
+
if (!config.version) errors.push("Theme version is required");
|
|
83
|
+
if (!config.author) errors.push("Theme author is required");
|
|
84
|
+
if (config.version && !/^\d+\.\d+\.\d+$/.test(config.version)) {
|
|
85
|
+
errors.push("Version must follow semantic versioning (e.g., 1.0.0)");
|
|
86
|
+
}
|
|
87
|
+
if (config.id && !/^[a-z0-9-]+$/.test(config.id)) {
|
|
88
|
+
errors.push("Theme ID must contain only lowercase letters, numbers, and hyphens");
|
|
89
|
+
}
|
|
90
|
+
if (!config.screenshots || config.screenshots.length === 0) {
|
|
91
|
+
warnings.push("At least one screenshot is recommended");
|
|
92
|
+
}
|
|
93
|
+
if (config.pricing === "paid" && !config.price) {
|
|
94
|
+
errors.push("Price is required for paid themes");
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
valid: errors.length === 0,
|
|
98
|
+
errors,
|
|
99
|
+
warnings
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function isValidVersion(version) {
|
|
103
|
+
return /^\d+\.\d+\.\d+$/.test(version);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
exports.cn = cn;
|
|
107
|
+
exports.formatDate = formatDate;
|
|
108
|
+
exports.getAvatarColor = getAvatarColor;
|
|
109
|
+
exports.getUserInitials = getUserInitials;
|
|
110
|
+
exports.isActiveRoute = isActiveRoute;
|
|
111
|
+
exports.isValidVersion = isValidVersion;
|
|
112
|
+
exports.validateThemeConfig = validateThemeConfig;
|
|
113
|
+
//# sourceMappingURL=index.js.map
|
|
114
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/styling.ts","../src/utils/user.ts","../src/utils/formatting.ts","../src/utils/navigation.ts","../src/utils/validation.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export function cn(...classes: (string | undefined | null | false | 0)[]): string {\r\n return classes.filter(Boolean).join(' ');\r\n}\r\n\r\nexport function getAvatarColor(userId: string): string {\r\n const colors = [\r\n '#4F46E5', '#7C3AED', '#DB2777', '#DC2626',\r\n '#EA580C', '#CA8A04', '#16A34A', '#0891B2', '#2563EB'\r\n ];\r\n \r\n let hash = 0;\r\n for (let i = 0; i < userId.length; i++) {\r\n hash = userId.charCodeAt(i) + ((hash << 5) - hash);\r\n }\r\n \r\n return colors[Math.abs(hash) % colors.length];\r\n}\r\n","export function getUserInitials(name: string): string {\r\n if (!name || typeof name !== 'string') return '?';\r\n \r\n return name\r\n .trim()\r\n .split(/\\s+/)\r\n .filter(Boolean)\r\n .map(word => word[0])\r\n .join('')\r\n .toUpperCase()\r\n .slice(0, 2);\r\n}\r\n","export function formatDate(\r\n date: Date | string,\r\n format: 'short' | 'long' | 'relative' = 'short'\r\n): string {\r\n const dateObj = typeof date === 'string' ? new Date(date) : date;\r\n \r\n if (isNaN(dateObj.getTime())) return 'Invalid date';\r\n \r\n switch (format) {\r\n case 'short':\r\n return new Intl.DateTimeFormat('en-US', {\r\n month: 'short',\r\n day: 'numeric',\r\n year: 'numeric',\r\n }).format(dateObj);\r\n \r\n case 'long':\r\n return new Intl.DateTimeFormat('en-US', {\r\n month: 'long',\r\n day: 'numeric',\r\n year: 'numeric',\r\n hour: 'numeric',\r\n minute: '2-digit',\r\n }).format(dateObj);\r\n \r\n case 'relative':\r\n const now = Date.now();\r\n const diff = now - dateObj.getTime();\r\n const seconds = Math.floor(diff / 1000);\r\n const minutes = Math.floor(seconds / 60);\r\n const hours = Math.floor(minutes / 60);\r\n const days = Math.floor(hours / 24);\r\n \r\n if (seconds < 60) return 'Just now';\r\n if (minutes < 60) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;\r\n if (hours < 24) return `${hours} hour${hours > 1 ? 's' : ''} ago`;\r\n if (days < 7) return `${days} day${days > 1 ? 's' : ''} ago`;\r\n \r\n return formatDate(dateObj, 'short');\r\n \r\n default:\r\n return dateObj.toLocaleDateString();\r\n }\r\n}\r\n","export function isActiveRoute(currentPath: string, itemPath: string): boolean {\r\n if (!currentPath || !itemPath) return false;\r\n if (currentPath === itemPath) return true;\r\n if (itemPath !== '/' && currentPath.startsWith(itemPath)) return true;\r\n return false;\r\n}\r\n","import type { ThemeManifest } from '../types';\r\n\r\nexport interface ValidationResult {\r\n valid: boolean;\r\n errors: string[];\r\n warnings: string[];\r\n}\r\n\r\nexport function validateThemeConfig(config: ThemeManifest): ValidationResult {\r\n const errors: string[] = [];\r\n const warnings: string[] = [];\r\n \r\n if (!config.id) errors.push('Theme ID is required');\r\n if (!config.name) errors.push('Theme name is required');\r\n if (!config.version) errors.push('Theme version is required');\r\n if (!config.author) errors.push('Theme author is required');\r\n \r\n if (config.version && !/^\\d+\\.\\d+\\.\\d+$/.test(config.version)) {\r\n errors.push('Version must follow semantic versioning (e.g., 1.0.0)');\r\n }\r\n \r\n if (config.id && !/^[a-z0-9-]+$/.test(config.id)) {\r\n errors.push('Theme ID must contain only lowercase letters, numbers, and hyphens');\r\n }\r\n \r\n if (!config.screenshots || config.screenshots.length === 0) {\r\n warnings.push('At least one screenshot is recommended');\r\n }\r\n \r\n if (config.pricing === 'paid' && !config.price) {\r\n errors.push('Price is required for paid themes');\r\n }\r\n \r\n return {\r\n valid: errors.length === 0,\r\n errors,\r\n warnings,\r\n };\r\n}\r\n\r\nexport function isValidVersion(version: string): boolean {\r\n return /^\\d+\\.\\d+\\.\\d+$/.test(version);\r\n}\r\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// src/utils/styling.ts
|
|
2
|
+
function cn(...classes) {
|
|
3
|
+
return classes.filter(Boolean).join(" ");
|
|
4
|
+
}
|
|
5
|
+
function getAvatarColor(userId) {
|
|
6
|
+
const colors = [
|
|
7
|
+
"#4F46E5",
|
|
8
|
+
"#7C3AED",
|
|
9
|
+
"#DB2777",
|
|
10
|
+
"#DC2626",
|
|
11
|
+
"#EA580C",
|
|
12
|
+
"#CA8A04",
|
|
13
|
+
"#16A34A",
|
|
14
|
+
"#0891B2",
|
|
15
|
+
"#2563EB"
|
|
16
|
+
];
|
|
17
|
+
let hash = 0;
|
|
18
|
+
for (let i = 0; i < userId.length; i++) {
|
|
19
|
+
hash = userId.charCodeAt(i) + ((hash << 5) - hash);
|
|
20
|
+
}
|
|
21
|
+
return colors[Math.abs(hash) % colors.length];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/utils/user.ts
|
|
25
|
+
function getUserInitials(name) {
|
|
26
|
+
if (!name || typeof name !== "string") return "?";
|
|
27
|
+
return name.trim().split(/\s+/).filter(Boolean).map((word) => word[0]).join("").toUpperCase().slice(0, 2);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/utils/formatting.ts
|
|
31
|
+
function formatDate(date, format = "short") {
|
|
32
|
+
const dateObj = typeof date === "string" ? new Date(date) : date;
|
|
33
|
+
if (isNaN(dateObj.getTime())) return "Invalid date";
|
|
34
|
+
switch (format) {
|
|
35
|
+
case "short":
|
|
36
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
37
|
+
month: "short",
|
|
38
|
+
day: "numeric",
|
|
39
|
+
year: "numeric"
|
|
40
|
+
}).format(dateObj);
|
|
41
|
+
case "long":
|
|
42
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
43
|
+
month: "long",
|
|
44
|
+
day: "numeric",
|
|
45
|
+
year: "numeric",
|
|
46
|
+
hour: "numeric",
|
|
47
|
+
minute: "2-digit"
|
|
48
|
+
}).format(dateObj);
|
|
49
|
+
case "relative":
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
const diff = now - dateObj.getTime();
|
|
52
|
+
const seconds = Math.floor(diff / 1e3);
|
|
53
|
+
const minutes = Math.floor(seconds / 60);
|
|
54
|
+
const hours = Math.floor(minutes / 60);
|
|
55
|
+
const days = Math.floor(hours / 24);
|
|
56
|
+
if (seconds < 60) return "Just now";
|
|
57
|
+
if (minutes < 60) return `${minutes} minute${minutes > 1 ? "s" : ""} ago`;
|
|
58
|
+
if (hours < 24) return `${hours} hour${hours > 1 ? "s" : ""} ago`;
|
|
59
|
+
if (days < 7) return `${days} day${days > 1 ? "s" : ""} ago`;
|
|
60
|
+
return formatDate(dateObj, "short");
|
|
61
|
+
default:
|
|
62
|
+
return dateObj.toLocaleDateString();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/utils/navigation.ts
|
|
67
|
+
function isActiveRoute(currentPath, itemPath) {
|
|
68
|
+
if (!currentPath || !itemPath) return false;
|
|
69
|
+
if (currentPath === itemPath) return true;
|
|
70
|
+
if (itemPath !== "/" && currentPath.startsWith(itemPath)) return true;
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/utils/validation.ts
|
|
75
|
+
function validateThemeConfig(config) {
|
|
76
|
+
const errors = [];
|
|
77
|
+
const warnings = [];
|
|
78
|
+
if (!config.id) errors.push("Theme ID is required");
|
|
79
|
+
if (!config.name) errors.push("Theme name is required");
|
|
80
|
+
if (!config.version) errors.push("Theme version is required");
|
|
81
|
+
if (!config.author) errors.push("Theme author is required");
|
|
82
|
+
if (config.version && !/^\d+\.\d+\.\d+$/.test(config.version)) {
|
|
83
|
+
errors.push("Version must follow semantic versioning (e.g., 1.0.0)");
|
|
84
|
+
}
|
|
85
|
+
if (config.id && !/^[a-z0-9-]+$/.test(config.id)) {
|
|
86
|
+
errors.push("Theme ID must contain only lowercase letters, numbers, and hyphens");
|
|
87
|
+
}
|
|
88
|
+
if (!config.screenshots || config.screenshots.length === 0) {
|
|
89
|
+
warnings.push("At least one screenshot is recommended");
|
|
90
|
+
}
|
|
91
|
+
if (config.pricing === "paid" && !config.price) {
|
|
92
|
+
errors.push("Price is required for paid themes");
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
valid: errors.length === 0,
|
|
96
|
+
errors,
|
|
97
|
+
warnings
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function isValidVersion(version) {
|
|
101
|
+
return /^\d+\.\d+\.\d+$/.test(version);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export { cn, formatDate, getAvatarColor, getUserInitials, isActiveRoute, isValidVersion, validateThemeConfig };
|
|
105
|
+
//# sourceMappingURL=index.mjs.map
|
|
106
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/styling.ts","../src/utils/user.ts","../src/utils/formatting.ts","../src/utils/navigation.ts","../src/utils/validation.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["export function cn(...classes: (string | undefined | null | false | 0)[]): string {\r\n return classes.filter(Boolean).join(' ');\r\n}\r\n\r\nexport function getAvatarColor(userId: string): string {\r\n const colors = [\r\n '#4F46E5', '#7C3AED', '#DB2777', '#DC2626',\r\n '#EA580C', '#CA8A04', '#16A34A', '#0891B2', '#2563EB'\r\n ];\r\n \r\n let hash = 0;\r\n for (let i = 0; i < userId.length; i++) {\r\n hash = userId.charCodeAt(i) + ((hash << 5) - hash);\r\n }\r\n \r\n return colors[Math.abs(hash) % colors.length];\r\n}\r\n","export function getUserInitials(name: string): string {\r\n if (!name || typeof name !== 'string') return '?';\r\n \r\n return name\r\n .trim()\r\n .split(/\\s+/)\r\n .filter(Boolean)\r\n .map(word => word[0])\r\n .join('')\r\n .toUpperCase()\r\n .slice(0, 2);\r\n}\r\n","export function formatDate(\r\n date: Date | string,\r\n format: 'short' | 'long' | 'relative' = 'short'\r\n): string {\r\n const dateObj = typeof date === 'string' ? new Date(date) : date;\r\n \r\n if (isNaN(dateObj.getTime())) return 'Invalid date';\r\n \r\n switch (format) {\r\n case 'short':\r\n return new Intl.DateTimeFormat('en-US', {\r\n month: 'short',\r\n day: 'numeric',\r\n year: 'numeric',\r\n }).format(dateObj);\r\n \r\n case 'long':\r\n return new Intl.DateTimeFormat('en-US', {\r\n month: 'long',\r\n day: 'numeric',\r\n year: 'numeric',\r\n hour: 'numeric',\r\n minute: '2-digit',\r\n }).format(dateObj);\r\n \r\n case 'relative':\r\n const now = Date.now();\r\n const diff = now - dateObj.getTime();\r\n const seconds = Math.floor(diff / 1000);\r\n const minutes = Math.floor(seconds / 60);\r\n const hours = Math.floor(minutes / 60);\r\n const days = Math.floor(hours / 24);\r\n \r\n if (seconds < 60) return 'Just now';\r\n if (minutes < 60) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;\r\n if (hours < 24) return `${hours} hour${hours > 1 ? 's' : ''} ago`;\r\n if (days < 7) return `${days} day${days > 1 ? 's' : ''} ago`;\r\n \r\n return formatDate(dateObj, 'short');\r\n \r\n default:\r\n return dateObj.toLocaleDateString();\r\n }\r\n}\r\n","export function isActiveRoute(currentPath: string, itemPath: string): boolean {\r\n if (!currentPath || !itemPath) return false;\r\n if (currentPath === itemPath) return true;\r\n if (itemPath !== '/' && currentPath.startsWith(itemPath)) return true;\r\n return false;\r\n}\r\n","import type { ThemeManifest } from '../types';\r\n\r\nexport interface ValidationResult {\r\n valid: boolean;\r\n errors: string[];\r\n warnings: string[];\r\n}\r\n\r\nexport function validateThemeConfig(config: ThemeManifest): ValidationResult {\r\n const errors: string[] = [];\r\n const warnings: string[] = [];\r\n \r\n if (!config.id) errors.push('Theme ID is required');\r\n if (!config.name) errors.push('Theme name is required');\r\n if (!config.version) errors.push('Theme version is required');\r\n if (!config.author) errors.push('Theme author is required');\r\n \r\n if (config.version && !/^\\d+\\.\\d+\\.\\d+$/.test(config.version)) {\r\n errors.push('Version must follow semantic versioning (e.g., 1.0.0)');\r\n }\r\n \r\n if (config.id && !/^[a-z0-9-]+$/.test(config.id)) {\r\n errors.push('Theme ID must contain only lowercase letters, numbers, and hyphens');\r\n }\r\n \r\n if (!config.screenshots || config.screenshots.length === 0) {\r\n warnings.push('At least one screenshot is recommended');\r\n }\r\n \r\n if (config.pricing === 'paid' && !config.price) {\r\n errors.push('Price is required for paid themes');\r\n }\r\n \r\n return {\r\n valid: errors.length === 0,\r\n errors,\r\n warnings,\r\n };\r\n}\r\n\r\nexport function isValidVersion(version: string): boolean {\r\n return /^\\d+\\.\\d+\\.\\d+$/.test(version);\r\n}\r\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ankit.blumox/theme-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official SDK for building custom themes for Blumox platform",
|
|
5
|
+
"keywords": ["blumox", "theme", "sdk", "nextjs", "react"],
|
|
6
|
+
"author": "Blumox Team",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.mjs",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"templates"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"dev": "tsup --watch",
|
|
25
|
+
"type-check": "tsc --noEmit"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"react": "^19.0.0",
|
|
29
|
+
"react-dom": "^19.0.0",
|
|
30
|
+
"next": "^16.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.0.0",
|
|
34
|
+
"@types/react": "^19.0.0",
|
|
35
|
+
"@types/react-dom": "^19.0.0",
|
|
36
|
+
"next": "^16.0.0",
|
|
37
|
+
"react": "^19.0.0",
|
|
38
|
+
"react-dom": "^19.0.0",
|
|
39
|
+
"tsup": "^8.0.0",
|
|
40
|
+
"typescript": "^5.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ThemeLayoutProps } from '@blumox/theme-sdk';
|
|
2
|
+
import { getUserInitials, cn, isActiveRoute } from '@blumox/theme-sdk';
|
|
3
|
+
|
|
4
|
+
export default function StarterTheme({
|
|
5
|
+
children,
|
|
6
|
+
navigation,
|
|
7
|
+
user,
|
|
8
|
+
tenant,
|
|
9
|
+
}: ThemeLayoutProps) {
|
|
10
|
+
const initials = getUserInitials(user.name);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="theme-container">
|
|
14
|
+
<header className="header">
|
|
15
|
+
<div className="brand">
|
|
16
|
+
<h1>{tenant.tenantName}</h1>
|
|
17
|
+
</div>
|
|
18
|
+
<div className="user-info">
|
|
19
|
+
<div className="avatar">{initials}</div>
|
|
20
|
+
<span>{user.name}</span>
|
|
21
|
+
</div>
|
|
22
|
+
</header>
|
|
23
|
+
|
|
24
|
+
<nav className="navigation">
|
|
25
|
+
{navigation.items.map((item) => (
|
|
26
|
+
<a
|
|
27
|
+
key={item.path}
|
|
28
|
+
href={item.path}
|
|
29
|
+
className={cn(
|
|
30
|
+
'nav-item',
|
|
31
|
+
isActiveRoute(window.location.pathname, item.path) && 'active'
|
|
32
|
+
)}
|
|
33
|
+
>
|
|
34
|
+
{item.label}
|
|
35
|
+
{item.badge && <span className="badge">{item.badge}</span>}
|
|
36
|
+
</a>
|
|
37
|
+
))}
|
|
38
|
+
</nav>
|
|
39
|
+
|
|
40
|
+
<main className="content">{children}</main>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @blumox/theme-sdk
|
|
2
|
+
|
|
3
|
+
Official TypeScript SDK for building custom themes for the Blumox multi-tenant SaaS platform.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Blumox Theme SDK enables developers to create custom themes for the Blumox platform using a revolutionary "liquid architecture" where themes control 100% of the layout and design while the platform provides data [file:1]. Build once, deploy to thousands of tenants across the marketplace.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @blumox/theme-sdk
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--primary-color: #4F46E5;
|
|
3
|
+
--background-color: #ffffff;
|
|
4
|
+
--text-color: #1F2937;
|
|
5
|
+
--border-color: #E5E7EB;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.theme-container {
|
|
9
|
+
min-height: 100vh;
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.header {
|
|
15
|
+
display: flex;
|
|
16
|
+
justify-content: space-between;
|
|
17
|
+
padding: 1rem 2rem;
|
|
18
|
+
border-bottom: 1px solid var(--border-color);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.avatar {
|
|
22
|
+
width: 40px;
|
|
23
|
+
height: 40px;
|
|
24
|
+
border-radius: 50%;
|
|
25
|
+
background: var(--primary-color);
|
|
26
|
+
color: white;
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: center;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.navigation {
|
|
33
|
+
display: flex;
|
|
34
|
+
gap: 1rem;
|
|
35
|
+
padding: 1rem 2rem;
|
|
36
|
+
border-bottom: 1px solid var(--border-color);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.nav-item {
|
|
40
|
+
padding: 0.5rem 1rem;
|
|
41
|
+
text-decoration: none;
|
|
42
|
+
color: var(--text-color);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.nav-item.active {
|
|
46
|
+
background: var(--primary-color);
|
|
47
|
+
color: white;
|
|
48
|
+
border-radius: 0.375rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.content {
|
|
52
|
+
flex: 1;
|
|
53
|
+
padding: 2rem;
|
|
54
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ThemeManifest } from '@blumox/theme-sdk';
|
|
2
|
+
|
|
3
|
+
export const themeConfig: ThemeManifest = {
|
|
4
|
+
id: 'my-custom-theme',
|
|
5
|
+
name: 'My Custom Theme',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
author: 'Your Name',
|
|
8
|
+
description: 'A beautiful custom theme for Blumox platform',
|
|
9
|
+
screenshots: [],
|
|
10
|
+
category: 'modern',
|
|
11
|
+
pricing: 'free',
|
|
12
|
+
};
|