theme-ops-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 +83 -0
- package/dist/index.cjs +140 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +138 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# theme-ops-sdk
|
|
2
|
+
|
|
3
|
+
React SDK for Theme Ops — fetch and apply themes dynamically from CDN.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install theme-ops-sdk
|
|
9
|
+
# or
|
|
10
|
+
pnpm add theme-ops-sdk
|
|
11
|
+
# or
|
|
12
|
+
yarn add theme-ops-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Peer dependencies** (must be installed in your project):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install react react-dom
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { useThemeOps } from 'theme-ops-sdk';
|
|
25
|
+
|
|
26
|
+
function App() {
|
|
27
|
+
const { isLoading, theme, insertStyles } = useThemeOps({
|
|
28
|
+
organizationId: 'your-org-id',
|
|
29
|
+
appId: 'your-app-id', // optional
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Insert CSS stylesheet into the document head
|
|
33
|
+
insertStyles();
|
|
34
|
+
|
|
35
|
+
if (isLoading) return <div>Loading theme...</div>;
|
|
36
|
+
|
|
37
|
+
// Use theme tokens with your design system (e.g. Ant Design)
|
|
38
|
+
const lightTokens = theme('light');
|
|
39
|
+
const darkTokens = theme('dark');
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<ConfigProvider theme={lightTokens}>
|
|
43
|
+
<YourApp />
|
|
44
|
+
</ConfigProvider>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
### `useThemeOps(options)`
|
|
52
|
+
|
|
53
|
+
Main hook that fetches theme assets from the Theme Ops CDN.
|
|
54
|
+
|
|
55
|
+
#### Options
|
|
56
|
+
|
|
57
|
+
| Parameter | Type | Required | Description |
|
|
58
|
+
| ---------------- | -------- | -------- | ------------------------------------------ |
|
|
59
|
+
| `organizationId` | `string` | Yes | Your organization identifier |
|
|
60
|
+
| `appId` | `string` | No | App-specific identifier within the org |
|
|
61
|
+
|
|
62
|
+
#### Returns
|
|
63
|
+
|
|
64
|
+
| Property | Type | Description |
|
|
65
|
+
| -------------- | -------------------------------------------- | -------------------------------------------------------- |
|
|
66
|
+
| `isLoading` | `boolean` | `true` while fetching theme assets from the CDN |
|
|
67
|
+
| `theme` | `(mode: 'light' \| 'dark') => ThemeConfig` | Returns design tokens for the given color mode |
|
|
68
|
+
| `insertStyles` | `() => void` | Inserts a `<link>` stylesheet into `document.head` |
|
|
69
|
+
|
|
70
|
+
## Types
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import type {
|
|
74
|
+
ThemeConfig,
|
|
75
|
+
ThemeTokens,
|
|
76
|
+
UseThemeOpsOptions,
|
|
77
|
+
UseThemeOpsResult,
|
|
78
|
+
} from 'theme-ops-sdk';
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/useThemeOps.ts
|
|
6
|
+
|
|
7
|
+
// src/buildCdnUrls.ts
|
|
8
|
+
var CDN_BASE_URL = "https://cdn.themeops.xyz";
|
|
9
|
+
function buildBasePath(organizationId, appId) {
|
|
10
|
+
return appId ? `${organizationId}/${appId}` : organizationId;
|
|
11
|
+
}
|
|
12
|
+
function buildCdnUrls({
|
|
13
|
+
organizationId,
|
|
14
|
+
appId
|
|
15
|
+
}) {
|
|
16
|
+
const assetPath = buildBasePath(organizationId, appId);
|
|
17
|
+
return {
|
|
18
|
+
theme: `${CDN_BASE_URL}/${assetPath}/theme.json`,
|
|
19
|
+
themeFallback: `${CDN_BASE_URL}/${assetPath}/theme-fallback.json`,
|
|
20
|
+
css: `${CDN_BASE_URL}/${assetPath}/css.css`
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/fetchThemeAssets.ts
|
|
25
|
+
async function fetchJson(url) {
|
|
26
|
+
try {
|
|
27
|
+
const response = await fetch(url);
|
|
28
|
+
if (!response.ok) return null;
|
|
29
|
+
return await response.json();
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function extractThemesFromJson(json) {
|
|
35
|
+
return {
|
|
36
|
+
themeLight: json?.light ?? null,
|
|
37
|
+
themeDark: json?.dark ?? null
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function isThemeJsonValid(json) {
|
|
41
|
+
return json !== null && (json.light !== void 0 || json.dark !== void 0);
|
|
42
|
+
}
|
|
43
|
+
async function fetchThemeAssets(urls) {
|
|
44
|
+
const activeJson = await fetchJson(urls.theme);
|
|
45
|
+
if (isThemeJsonValid(activeJson)) {
|
|
46
|
+
const { themeLight: themeLight2, themeDark } = extractThemesFromJson(activeJson);
|
|
47
|
+
return { themeLight: themeLight2, themeDark, cssUrl: urls.css, usingFallback: false };
|
|
48
|
+
}
|
|
49
|
+
const fallbackJson = await fetchJson(urls.themeFallback);
|
|
50
|
+
const { themeLight } = extractThemesFromJson(fallbackJson);
|
|
51
|
+
return {
|
|
52
|
+
themeLight,
|
|
53
|
+
themeDark: null,
|
|
54
|
+
cssUrl: urls.css,
|
|
55
|
+
usingFallback: true
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/insertStylesIntoDOM.ts
|
|
60
|
+
var STYLE_ELEMENT_ID = "theme-ops-css";
|
|
61
|
+
function findExistingStyleElement() {
|
|
62
|
+
return document.getElementById(STYLE_ELEMENT_ID);
|
|
63
|
+
}
|
|
64
|
+
function createStyleElement(href) {
|
|
65
|
+
const link = document.createElement("link");
|
|
66
|
+
link.id = STYLE_ELEMENT_ID;
|
|
67
|
+
link.rel = "stylesheet";
|
|
68
|
+
link.href = href;
|
|
69
|
+
return link;
|
|
70
|
+
}
|
|
71
|
+
function insertStylesIntoDOM(cssUrl) {
|
|
72
|
+
const existing = findExistingStyleElement();
|
|
73
|
+
if (existing) {
|
|
74
|
+
existing.href = cssUrl;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const link = createStyleElement(cssUrl);
|
|
78
|
+
document.head.appendChild(link);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/useThemeOps.ts
|
|
82
|
+
function hookReducer(_state, action) {
|
|
83
|
+
switch (action.type) {
|
|
84
|
+
case "FETCH_START":
|
|
85
|
+
return { status: "loading" };
|
|
86
|
+
case "FETCH_SUCCESS":
|
|
87
|
+
return { status: "ready", themeState: action.payload };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
var EMPTY_THEME_STATE = {
|
|
91
|
+
themeLight: null,
|
|
92
|
+
themeDark: null,
|
|
93
|
+
cssUrl: null
|
|
94
|
+
};
|
|
95
|
+
function resolveThemeTokens(mode, themeState) {
|
|
96
|
+
const tokens = mode === "dark" ? themeState.themeDark : themeState.themeLight;
|
|
97
|
+
if (!tokens) return {};
|
|
98
|
+
return { token: tokens };
|
|
99
|
+
}
|
|
100
|
+
function useThemeOps({
|
|
101
|
+
organizationId,
|
|
102
|
+
appId
|
|
103
|
+
}) {
|
|
104
|
+
const [state, dispatch] = react.useReducer(hookReducer, { status: "loading" });
|
|
105
|
+
const isMountedRef = react.useRef(true);
|
|
106
|
+
react.useEffect(() => {
|
|
107
|
+
isMountedRef.current = true;
|
|
108
|
+
dispatch({ type: "FETCH_START" });
|
|
109
|
+
const urls = buildCdnUrls({ organizationId, appId });
|
|
110
|
+
fetchThemeAssets(urls).then((assets) => {
|
|
111
|
+
if (!isMountedRef.current) return;
|
|
112
|
+
dispatch({
|
|
113
|
+
type: "FETCH_SUCCESS",
|
|
114
|
+
payload: {
|
|
115
|
+
themeLight: assets.themeLight,
|
|
116
|
+
themeDark: assets.themeDark,
|
|
117
|
+
cssUrl: assets.cssUrl
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
return () => {
|
|
122
|
+
isMountedRef.current = false;
|
|
123
|
+
};
|
|
124
|
+
}, [organizationId, appId]);
|
|
125
|
+
const resolvedThemeState = state.status === "ready" ? state.themeState : EMPTY_THEME_STATE;
|
|
126
|
+
const theme = react.useCallback(
|
|
127
|
+
(mode) => resolveThemeTokens(mode, resolvedThemeState),
|
|
128
|
+
[resolvedThemeState]
|
|
129
|
+
);
|
|
130
|
+
const insertStyles = react.useCallback(() => {
|
|
131
|
+
if (resolvedThemeState.cssUrl) {
|
|
132
|
+
insertStylesIntoDOM(resolvedThemeState.cssUrl);
|
|
133
|
+
}
|
|
134
|
+
}, [resolvedThemeState.cssUrl]);
|
|
135
|
+
return { isLoading: state.status === "loading", theme, insertStyles };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
exports.useThemeOps = useThemeOps;
|
|
139
|
+
//# sourceMappingURL=index.cjs.map
|
|
140
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/buildCdnUrls.ts","../src/fetchThemeAssets.ts","../src/insertStylesIntoDOM.ts","../src/useThemeOps.ts"],"names":["themeLight","useReducer","useRef","useEffect","useCallback"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { BuildCdnUrlsOptions, CdnUrls } from \"./types\";\n\nconst CDN_BASE_URL = \"https://cdn.themeops.xyz\";\n\nfunction buildBasePath(organizationId: string, appId?: string): string {\n return appId ? `${organizationId}/${appId}` : organizationId;\n}\n\nexport function buildCdnUrls({\n organizationId,\n appId,\n}: BuildCdnUrlsOptions): CdnUrls {\n const assetPath = buildBasePath(organizationId, appId);\n\n return {\n theme: `${CDN_BASE_URL}/${assetPath}/theme.json`,\n themeFallback: `${CDN_BASE_URL}/${assetPath}/theme-fallback.json`,\n css: `${CDN_BASE_URL}/${assetPath}/css.css`,\n };\n}\n","import type { CdnUrls, ThemeAssets, ThemeJson, ThemeTokens } from \"./types\";\n\nasync function fetchJson<T>(url: string): Promise<T | null> {\n try {\n const response = await fetch(url);\n if (!response.ok) return null;\n\n return (await response.json()) as T;\n } catch {\n return null;\n }\n}\n\nfunction extractThemesFromJson(json: ThemeJson | null): {\n themeLight: ThemeTokens | null;\n themeDark: ThemeTokens | null;\n} {\n return {\n themeLight: json?.light ?? null,\n themeDark: json?.dark ?? null,\n };\n}\n\nfunction isThemeJsonValid(json: ThemeJson | null): boolean {\n return json !== null && (json.light !== undefined || json.dark !== undefined);\n}\n\nexport async function fetchThemeAssets(urls: CdnUrls): Promise<ThemeAssets> {\n const activeJson = await fetchJson<ThemeJson>(urls.theme);\n\n if (isThemeJsonValid(activeJson)) {\n const { themeLight, themeDark } = extractThemesFromJson(activeJson);\n return { themeLight, themeDark, cssUrl: urls.css, usingFallback: false };\n }\n\n const fallbackJson = await fetchJson<ThemeJson>(urls.themeFallback);\n const { themeLight } = extractThemesFromJson(fallbackJson);\n\n return {\n themeLight,\n themeDark: null,\n cssUrl: urls.css,\n usingFallback: true,\n };\n}\n","const STYLE_ELEMENT_ID = \"theme-ops-css\";\n\nfunction findExistingStyleElement(): HTMLLinkElement | null {\n return document.getElementById(STYLE_ELEMENT_ID) as HTMLLinkElement | null;\n}\n\nfunction createStyleElement(href: string): HTMLLinkElement {\n const link = document.createElement(\"link\");\n link.id = STYLE_ELEMENT_ID;\n link.rel = \"stylesheet\";\n link.href = href;\n return link;\n}\n\nexport function insertStylesIntoDOM(cssUrl: string): void {\n const existing = findExistingStyleElement();\n\n if (existing) {\n existing.href = cssUrl;\n return;\n }\n\n const link = createStyleElement(cssUrl);\n document.head.appendChild(link);\n}\n","import { useCallback, useEffect, useReducer, useRef } from \"react\";\n\nimport { buildCdnUrls } from \"./buildCdnUrls\";\nimport { fetchThemeAssets } from \"./fetchThemeAssets\";\nimport { insertStylesIntoDOM } from \"./insertStylesIntoDOM\";\nimport type {\n ThemeConfig,\n ThemeState,\n UseThemeOpsOptions,\n UseThemeOpsResult,\n} from \"./types\";\n\ntype HookState =\n | { status: \"loading\" }\n | { status: \"ready\"; themeState: ThemeState };\n\ntype HookAction =\n | { type: \"FETCH_START\" }\n | { type: \"FETCH_SUCCESS\"; payload: ThemeState };\n\nfunction hookReducer(_state: HookState, action: HookAction): HookState {\n switch (action.type) {\n case \"FETCH_START\":\n return { status: \"loading\" };\n case \"FETCH_SUCCESS\":\n return { status: \"ready\", themeState: action.payload };\n }\n}\n\nconst EMPTY_THEME_STATE: ThemeState = {\n themeLight: null,\n themeDark: null,\n cssUrl: null,\n};\n\nfunction resolveThemeTokens(\n mode: \"light\" | \"dark\",\n themeState: ThemeState,\n): ThemeConfig {\n const tokens = mode === \"dark\" ? themeState.themeDark : themeState.themeLight;\n if (!tokens) return {};\n\n return { token: tokens };\n}\n\nexport function useThemeOps({\n organizationId,\n appId,\n}: UseThemeOpsOptions): UseThemeOpsResult {\n const [state, dispatch] = useReducer(hookReducer, { status: \"loading\" });\n\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n\n dispatch({ type: \"FETCH_START\" });\n\n const urls = buildCdnUrls({ organizationId, appId });\n\n fetchThemeAssets(urls).then((assets) => {\n if (!isMountedRef.current) return;\n\n dispatch({\n type: \"FETCH_SUCCESS\",\n payload: {\n themeLight: assets.themeLight,\n themeDark: assets.themeDark,\n cssUrl: assets.cssUrl,\n },\n });\n });\n\n return () => {\n isMountedRef.current = false;\n };\n }, [organizationId, appId]);\n\n const resolvedThemeState =\n state.status === \"ready\" ? state.themeState : EMPTY_THEME_STATE;\n\n const theme = useCallback(\n (mode: \"light\" | \"dark\"): ThemeConfig =>\n resolveThemeTokens(mode, resolvedThemeState),\n [resolvedThemeState],\n );\n\n const insertStyles = useCallback(() => {\n if (resolvedThemeState.cssUrl) {\n insertStylesIntoDOM(resolvedThemeState.cssUrl);\n }\n }, [resolvedThemeState.cssUrl]);\n\n return { isLoading: state.status === \"loading\", theme, insertStyles };\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type ThemeTokens = Record<string, unknown>;
|
|
2
|
+
interface ThemeConfig {
|
|
3
|
+
token?: ThemeTokens;
|
|
4
|
+
}
|
|
5
|
+
interface UseThemeOpsOptions {
|
|
6
|
+
organizationId: string;
|
|
7
|
+
appId?: string;
|
|
8
|
+
}
|
|
9
|
+
interface UseThemeOpsResult {
|
|
10
|
+
isLoading: boolean;
|
|
11
|
+
theme: (mode: "light" | "dark") => ThemeConfig;
|
|
12
|
+
insertStyles: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare function useThemeOps({ organizationId, appId, }: UseThemeOpsOptions): UseThemeOpsResult;
|
|
16
|
+
|
|
17
|
+
export { type ThemeConfig, type ThemeTokens, type UseThemeOpsOptions, type UseThemeOpsResult, useThemeOps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type ThemeTokens = Record<string, unknown>;
|
|
2
|
+
interface ThemeConfig {
|
|
3
|
+
token?: ThemeTokens;
|
|
4
|
+
}
|
|
5
|
+
interface UseThemeOpsOptions {
|
|
6
|
+
organizationId: string;
|
|
7
|
+
appId?: string;
|
|
8
|
+
}
|
|
9
|
+
interface UseThemeOpsResult {
|
|
10
|
+
isLoading: boolean;
|
|
11
|
+
theme: (mode: "light" | "dark") => ThemeConfig;
|
|
12
|
+
insertStyles: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare function useThemeOps({ organizationId, appId, }: UseThemeOpsOptions): UseThemeOpsResult;
|
|
16
|
+
|
|
17
|
+
export { type ThemeConfig, type ThemeTokens, type UseThemeOpsOptions, type UseThemeOpsResult, useThemeOps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { useReducer, useRef, useEffect, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/useThemeOps.ts
|
|
4
|
+
|
|
5
|
+
// src/buildCdnUrls.ts
|
|
6
|
+
var CDN_BASE_URL = "https://cdn.themeops.xyz";
|
|
7
|
+
function buildBasePath(organizationId, appId) {
|
|
8
|
+
return appId ? `${organizationId}/${appId}` : organizationId;
|
|
9
|
+
}
|
|
10
|
+
function buildCdnUrls({
|
|
11
|
+
organizationId,
|
|
12
|
+
appId
|
|
13
|
+
}) {
|
|
14
|
+
const assetPath = buildBasePath(organizationId, appId);
|
|
15
|
+
return {
|
|
16
|
+
theme: `${CDN_BASE_URL}/${assetPath}/theme.json`,
|
|
17
|
+
themeFallback: `${CDN_BASE_URL}/${assetPath}/theme-fallback.json`,
|
|
18
|
+
css: `${CDN_BASE_URL}/${assetPath}/css.css`
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/fetchThemeAssets.ts
|
|
23
|
+
async function fetchJson(url) {
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(url);
|
|
26
|
+
if (!response.ok) return null;
|
|
27
|
+
return await response.json();
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function extractThemesFromJson(json) {
|
|
33
|
+
return {
|
|
34
|
+
themeLight: json?.light ?? null,
|
|
35
|
+
themeDark: json?.dark ?? null
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function isThemeJsonValid(json) {
|
|
39
|
+
return json !== null && (json.light !== void 0 || json.dark !== void 0);
|
|
40
|
+
}
|
|
41
|
+
async function fetchThemeAssets(urls) {
|
|
42
|
+
const activeJson = await fetchJson(urls.theme);
|
|
43
|
+
if (isThemeJsonValid(activeJson)) {
|
|
44
|
+
const { themeLight: themeLight2, themeDark } = extractThemesFromJson(activeJson);
|
|
45
|
+
return { themeLight: themeLight2, themeDark, cssUrl: urls.css, usingFallback: false };
|
|
46
|
+
}
|
|
47
|
+
const fallbackJson = await fetchJson(urls.themeFallback);
|
|
48
|
+
const { themeLight } = extractThemesFromJson(fallbackJson);
|
|
49
|
+
return {
|
|
50
|
+
themeLight,
|
|
51
|
+
themeDark: null,
|
|
52
|
+
cssUrl: urls.css,
|
|
53
|
+
usingFallback: true
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/insertStylesIntoDOM.ts
|
|
58
|
+
var STYLE_ELEMENT_ID = "theme-ops-css";
|
|
59
|
+
function findExistingStyleElement() {
|
|
60
|
+
return document.getElementById(STYLE_ELEMENT_ID);
|
|
61
|
+
}
|
|
62
|
+
function createStyleElement(href) {
|
|
63
|
+
const link = document.createElement("link");
|
|
64
|
+
link.id = STYLE_ELEMENT_ID;
|
|
65
|
+
link.rel = "stylesheet";
|
|
66
|
+
link.href = href;
|
|
67
|
+
return link;
|
|
68
|
+
}
|
|
69
|
+
function insertStylesIntoDOM(cssUrl) {
|
|
70
|
+
const existing = findExistingStyleElement();
|
|
71
|
+
if (existing) {
|
|
72
|
+
existing.href = cssUrl;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const link = createStyleElement(cssUrl);
|
|
76
|
+
document.head.appendChild(link);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/useThemeOps.ts
|
|
80
|
+
function hookReducer(_state, action) {
|
|
81
|
+
switch (action.type) {
|
|
82
|
+
case "FETCH_START":
|
|
83
|
+
return { status: "loading" };
|
|
84
|
+
case "FETCH_SUCCESS":
|
|
85
|
+
return { status: "ready", themeState: action.payload };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
var EMPTY_THEME_STATE = {
|
|
89
|
+
themeLight: null,
|
|
90
|
+
themeDark: null,
|
|
91
|
+
cssUrl: null
|
|
92
|
+
};
|
|
93
|
+
function resolveThemeTokens(mode, themeState) {
|
|
94
|
+
const tokens = mode === "dark" ? themeState.themeDark : themeState.themeLight;
|
|
95
|
+
if (!tokens) return {};
|
|
96
|
+
return { token: tokens };
|
|
97
|
+
}
|
|
98
|
+
function useThemeOps({
|
|
99
|
+
organizationId,
|
|
100
|
+
appId
|
|
101
|
+
}) {
|
|
102
|
+
const [state, dispatch] = useReducer(hookReducer, { status: "loading" });
|
|
103
|
+
const isMountedRef = useRef(true);
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
isMountedRef.current = true;
|
|
106
|
+
dispatch({ type: "FETCH_START" });
|
|
107
|
+
const urls = buildCdnUrls({ organizationId, appId });
|
|
108
|
+
fetchThemeAssets(urls).then((assets) => {
|
|
109
|
+
if (!isMountedRef.current) return;
|
|
110
|
+
dispatch({
|
|
111
|
+
type: "FETCH_SUCCESS",
|
|
112
|
+
payload: {
|
|
113
|
+
themeLight: assets.themeLight,
|
|
114
|
+
themeDark: assets.themeDark,
|
|
115
|
+
cssUrl: assets.cssUrl
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
return () => {
|
|
120
|
+
isMountedRef.current = false;
|
|
121
|
+
};
|
|
122
|
+
}, [organizationId, appId]);
|
|
123
|
+
const resolvedThemeState = state.status === "ready" ? state.themeState : EMPTY_THEME_STATE;
|
|
124
|
+
const theme = useCallback(
|
|
125
|
+
(mode) => resolveThemeTokens(mode, resolvedThemeState),
|
|
126
|
+
[resolvedThemeState]
|
|
127
|
+
);
|
|
128
|
+
const insertStyles = useCallback(() => {
|
|
129
|
+
if (resolvedThemeState.cssUrl) {
|
|
130
|
+
insertStylesIntoDOM(resolvedThemeState.cssUrl);
|
|
131
|
+
}
|
|
132
|
+
}, [resolvedThemeState.cssUrl]);
|
|
133
|
+
return { isLoading: state.status === "loading", theme, insertStyles };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export { useThemeOps };
|
|
137
|
+
//# sourceMappingURL=index.js.map
|
|
138
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/buildCdnUrls.ts","../src/fetchThemeAssets.ts","../src/insertStylesIntoDOM.ts","../src/useThemeOps.ts"],"names":["themeLight"],"mappings":"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","file":"index.js","sourcesContent":["import type { BuildCdnUrlsOptions, CdnUrls } from \"./types\";\n\nconst CDN_BASE_URL = \"https://cdn.themeops.xyz\";\n\nfunction buildBasePath(organizationId: string, appId?: string): string {\n return appId ? `${organizationId}/${appId}` : organizationId;\n}\n\nexport function buildCdnUrls({\n organizationId,\n appId,\n}: BuildCdnUrlsOptions): CdnUrls {\n const assetPath = buildBasePath(organizationId, appId);\n\n return {\n theme: `${CDN_BASE_URL}/${assetPath}/theme.json`,\n themeFallback: `${CDN_BASE_URL}/${assetPath}/theme-fallback.json`,\n css: `${CDN_BASE_URL}/${assetPath}/css.css`,\n };\n}\n","import type { CdnUrls, ThemeAssets, ThemeJson, ThemeTokens } from \"./types\";\n\nasync function fetchJson<T>(url: string): Promise<T | null> {\n try {\n const response = await fetch(url);\n if (!response.ok) return null;\n\n return (await response.json()) as T;\n } catch {\n return null;\n }\n}\n\nfunction extractThemesFromJson(json: ThemeJson | null): {\n themeLight: ThemeTokens | null;\n themeDark: ThemeTokens | null;\n} {\n return {\n themeLight: json?.light ?? null,\n themeDark: json?.dark ?? null,\n };\n}\n\nfunction isThemeJsonValid(json: ThemeJson | null): boolean {\n return json !== null && (json.light !== undefined || json.dark !== undefined);\n}\n\nexport async function fetchThemeAssets(urls: CdnUrls): Promise<ThemeAssets> {\n const activeJson = await fetchJson<ThemeJson>(urls.theme);\n\n if (isThemeJsonValid(activeJson)) {\n const { themeLight, themeDark } = extractThemesFromJson(activeJson);\n return { themeLight, themeDark, cssUrl: urls.css, usingFallback: false };\n }\n\n const fallbackJson = await fetchJson<ThemeJson>(urls.themeFallback);\n const { themeLight } = extractThemesFromJson(fallbackJson);\n\n return {\n themeLight,\n themeDark: null,\n cssUrl: urls.css,\n usingFallback: true,\n };\n}\n","const STYLE_ELEMENT_ID = \"theme-ops-css\";\n\nfunction findExistingStyleElement(): HTMLLinkElement | null {\n return document.getElementById(STYLE_ELEMENT_ID) as HTMLLinkElement | null;\n}\n\nfunction createStyleElement(href: string): HTMLLinkElement {\n const link = document.createElement(\"link\");\n link.id = STYLE_ELEMENT_ID;\n link.rel = \"stylesheet\";\n link.href = href;\n return link;\n}\n\nexport function insertStylesIntoDOM(cssUrl: string): void {\n const existing = findExistingStyleElement();\n\n if (existing) {\n existing.href = cssUrl;\n return;\n }\n\n const link = createStyleElement(cssUrl);\n document.head.appendChild(link);\n}\n","import { useCallback, useEffect, useReducer, useRef } from \"react\";\n\nimport { buildCdnUrls } from \"./buildCdnUrls\";\nimport { fetchThemeAssets } from \"./fetchThemeAssets\";\nimport { insertStylesIntoDOM } from \"./insertStylesIntoDOM\";\nimport type {\n ThemeConfig,\n ThemeState,\n UseThemeOpsOptions,\n UseThemeOpsResult,\n} from \"./types\";\n\ntype HookState =\n | { status: \"loading\" }\n | { status: \"ready\"; themeState: ThemeState };\n\ntype HookAction =\n | { type: \"FETCH_START\" }\n | { type: \"FETCH_SUCCESS\"; payload: ThemeState };\n\nfunction hookReducer(_state: HookState, action: HookAction): HookState {\n switch (action.type) {\n case \"FETCH_START\":\n return { status: \"loading\" };\n case \"FETCH_SUCCESS\":\n return { status: \"ready\", themeState: action.payload };\n }\n}\n\nconst EMPTY_THEME_STATE: ThemeState = {\n themeLight: null,\n themeDark: null,\n cssUrl: null,\n};\n\nfunction resolveThemeTokens(\n mode: \"light\" | \"dark\",\n themeState: ThemeState,\n): ThemeConfig {\n const tokens = mode === \"dark\" ? themeState.themeDark : themeState.themeLight;\n if (!tokens) return {};\n\n return { token: tokens };\n}\n\nexport function useThemeOps({\n organizationId,\n appId,\n}: UseThemeOpsOptions): UseThemeOpsResult {\n const [state, dispatch] = useReducer(hookReducer, { status: \"loading\" });\n\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n\n dispatch({ type: \"FETCH_START\" });\n\n const urls = buildCdnUrls({ organizationId, appId });\n\n fetchThemeAssets(urls).then((assets) => {\n if (!isMountedRef.current) return;\n\n dispatch({\n type: \"FETCH_SUCCESS\",\n payload: {\n themeLight: assets.themeLight,\n themeDark: assets.themeDark,\n cssUrl: assets.cssUrl,\n },\n });\n });\n\n return () => {\n isMountedRef.current = false;\n };\n }, [organizationId, appId]);\n\n const resolvedThemeState =\n state.status === \"ready\" ? state.themeState : EMPTY_THEME_STATE;\n\n const theme = useCallback(\n (mode: \"light\" | \"dark\"): ThemeConfig =>\n resolveThemeTokens(mode, resolvedThemeState),\n [resolvedThemeState],\n );\n\n const insertStyles = useCallback(() => {\n if (resolvedThemeState.cssUrl) {\n insertStylesIntoDOM(resolvedThemeState.cssUrl);\n }\n }, [resolvedThemeState.cssUrl]);\n\n return { isLoading: state.status === \"loading\", theme, insertStyles };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "theme-ops-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React SDK for Theme Ops — fetch and apply themes from CDN",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.cts",
|
|
18
|
+
"default": "./dist/index.cjs"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"keywords": [
|
|
28
|
+
"react",
|
|
29
|
+
"theme",
|
|
30
|
+
"design-tokens",
|
|
31
|
+
"cdn",
|
|
32
|
+
"sdk"
|
|
33
|
+
],
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/ffernandomoraes/theme-ops-monorepo"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"dev": "tsup --watch",
|
|
41
|
+
"lint": "eslint . --max-warnings 0",
|
|
42
|
+
"prepublishOnly": "pnpm run build"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"react": ">=18",
|
|
46
|
+
"react-dom": ">=18"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@theme-ops/eslint-config": "workspace:*",
|
|
50
|
+
"@theme-ops/tsconfig": "workspace:*",
|
|
51
|
+
"@types/react": "^19.0.0",
|
|
52
|
+
"eslint": "^10.0.0",
|
|
53
|
+
"tsup": "^8.4.0",
|
|
54
|
+
"typescript": "^5.9.3"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
}
|
|
59
|
+
}
|