@shellui/core 0.2.0-alpha.2 → 0.2.0-alpha.3
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shellui/core",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.3",
|
|
4
4
|
"description": "ShellUI Core - Core React application runtime",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"workbox-strategies": "^7.1.0",
|
|
59
59
|
"workbox-cacheable-response": "^7.1.0",
|
|
60
60
|
"workbox-expiration": "^7.1.0",
|
|
61
|
-
"@shellui/sdk": "0.2.0-alpha.
|
|
61
|
+
"@shellui/sdk": "0.2.0-alpha.3"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -6,12 +6,13 @@ import {
|
|
|
6
6
|
type Settings,
|
|
7
7
|
type SettingsNavigationItem,
|
|
8
8
|
type Appearance,
|
|
9
|
+
type SettingsAvailableTheme,
|
|
9
10
|
} from '@shellui/sdk';
|
|
10
11
|
import { SettingsContext } from './SettingsContext';
|
|
11
12
|
import { useConfig } from '../config/useConfig';
|
|
12
13
|
import { useTranslation } from 'react-i18next';
|
|
13
14
|
import type { NavigationItem, NavigationGroup, ShellUIConfig } from '../config/types';
|
|
14
|
-
import { getTheme, registerTheme } from '../theme/themes';
|
|
15
|
+
import { getTheme, getAllThemes, registerTheme } from '../theme/themes';
|
|
15
16
|
|
|
16
17
|
const logger = getLogger('shellcore');
|
|
17
18
|
|
|
@@ -40,6 +41,20 @@ function resolveColorMode(colorScheme: 'light' | 'dark' | 'system'): 'light' | '
|
|
|
40
41
|
return colorScheme === 'dark' ? 'dark' : 'light';
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
/** Convert font file URLs to absolute so iframes/modals on other ports or domains can load them. */
|
|
45
|
+
function toAbsoluteFontUrls(urls: string[]): string[] {
|
|
46
|
+
if (typeof window === 'undefined') return urls;
|
|
47
|
+
const origin = window.location.origin;
|
|
48
|
+
return urls.map((url) => {
|
|
49
|
+
const trimmed = url.trim();
|
|
50
|
+
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
|
|
51
|
+
return trimmed;
|
|
52
|
+
}
|
|
53
|
+
const path = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
54
|
+
return `${origin}${path}`;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
43
58
|
/**
|
|
44
59
|
* Build the full appearance object for settings propagation so apps receive all theme
|
|
45
60
|
* variable values and can style without knowing the theme name.
|
|
@@ -75,13 +90,29 @@ function getResolvedAppearanceForSettings(
|
|
|
75
90
|
...(themeDef.textShadow !== undefined && { textShadow: themeDef.textShadow }),
|
|
76
91
|
...(themeDef.lineHeight !== undefined && { lineHeight: themeDef.lineHeight }),
|
|
77
92
|
...(themeDef.fontFiles !== undefined &&
|
|
78
|
-
themeDef.fontFiles.length > 0 && {
|
|
93
|
+
themeDef.fontFiles.length > 0 && {
|
|
94
|
+
fontFiles: toAbsoluteFontUrls(themeDef.fontFiles),
|
|
95
|
+
}),
|
|
79
96
|
};
|
|
80
97
|
}
|
|
81
98
|
|
|
82
99
|
/**
|
|
83
|
-
*
|
|
84
|
-
|
|
100
|
+
* Map registered themes to the slim shape sent to sub-apps (name, displayName, colors, optional typography for preview).
|
|
101
|
+
*/
|
|
102
|
+
function getAvailableThemesForSettings(): SettingsAvailableTheme[] {
|
|
103
|
+
return getAllThemes().map((theme) => ({
|
|
104
|
+
name: theme.name,
|
|
105
|
+
displayName: theme.displayName,
|
|
106
|
+
colors: theme.colors,
|
|
107
|
+
...(theme.fontFamily !== undefined && { fontFamily: theme.fontFamily }),
|
|
108
|
+
...(theme.letterSpacing !== undefined && { letterSpacing: theme.letterSpacing }),
|
|
109
|
+
...(theme.textShadow !== undefined && { textShadow: theme.textShadow }),
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Build settings for propagation to iframes: inject navigation, full theme object,
|
|
115
|
+
* and list of available themes so apps can render theme pickers.
|
|
85
116
|
*/
|
|
86
117
|
function buildSettingsForPropagation(
|
|
87
118
|
settings: Settings,
|
|
@@ -93,6 +124,16 @@ function buildSettingsForPropagation(
|
|
|
93
124
|
...settings,
|
|
94
125
|
appearance: appearance ?? settings.appearance,
|
|
95
126
|
};
|
|
127
|
+
// Inject available themes when we have a resolved appearance (themes are already registered above)
|
|
128
|
+
if (result.appearance && typeof window !== 'undefined') {
|
|
129
|
+
result = {
|
|
130
|
+
...result,
|
|
131
|
+
appearance: {
|
|
132
|
+
...result.appearance,
|
|
133
|
+
availableThemes: getAvailableThemesForSettings(),
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
96
137
|
if (config?.navigation?.length) {
|
|
97
138
|
const items: SettingsNavigationItem[] = flattenNavigationItems(
|
|
98
139
|
config.navigation,
|
|
@@ -4,8 +4,9 @@ import { useConfig } from '../../config/useConfig';
|
|
|
4
4
|
import { Button } from '../../../components/ui/button';
|
|
5
5
|
import { ButtonGroup } from '../../../components/ui/button-group';
|
|
6
6
|
import { cn } from '../../../lib/utils';
|
|
7
|
-
import { useEffect, useState } from 'react';
|
|
7
|
+
import { useEffect, useState, useMemo } from 'react';
|
|
8
8
|
import { getAllThemes, registerTheme, type ThemeDefinition } from '../../theme/themes';
|
|
9
|
+
import type { SettingsAvailableTheme } from '@shellui/sdk';
|
|
9
10
|
|
|
10
11
|
const SunIcon = () => (
|
|
11
12
|
<svg
|
|
@@ -85,13 +86,19 @@ const MonitorIcon = () => (
|
|
|
85
86
|
</svg>
|
|
86
87
|
);
|
|
87
88
|
|
|
89
|
+
/** Theme-like shape used for preview (ThemeDefinition or SettingsAvailableTheme). */
|
|
90
|
+
type ThemePreviewItem = Pick<
|
|
91
|
+
ThemeDefinition | SettingsAvailableTheme,
|
|
92
|
+
'name' | 'displayName' | 'colors' | 'fontFamily' | 'letterSpacing' | 'textShadow'
|
|
93
|
+
>;
|
|
94
|
+
|
|
88
95
|
// Theme color preview component
|
|
89
96
|
const ThemePreview = ({
|
|
90
97
|
theme,
|
|
91
98
|
isSelected,
|
|
92
99
|
isDark,
|
|
93
100
|
}: {
|
|
94
|
-
theme:
|
|
101
|
+
theme: ThemePreviewItem;
|
|
95
102
|
isSelected: boolean;
|
|
96
103
|
isDark: boolean;
|
|
97
104
|
}) => {
|
|
@@ -169,18 +176,25 @@ export const Appearance = () => {
|
|
|
169
176
|
const currentTheme = settings.appearance?.colorScheme ?? 'system';
|
|
170
177
|
const currentThemeName = settings.appearance?.name ?? 'default';
|
|
171
178
|
|
|
172
|
-
const [
|
|
179
|
+
const [localThemes, setLocalThemes] = useState<ThemeDefinition[]>([]);
|
|
173
180
|
|
|
174
|
-
// Register custom themes from config and get all themes
|
|
181
|
+
// Register custom themes from config and get all themes (for shell context)
|
|
175
182
|
useEffect(() => {
|
|
176
183
|
if (config?.themes) {
|
|
177
184
|
config.themes.forEach((themeDef: ThemeDefinition) => {
|
|
178
185
|
registerTheme(themeDef);
|
|
179
186
|
});
|
|
180
187
|
}
|
|
181
|
-
|
|
188
|
+
setLocalThemes(getAllThemes());
|
|
182
189
|
}, [config]);
|
|
183
190
|
|
|
191
|
+
// Use availableThemes from settings when provided (e.g. from shell when in sub-app), else local registry
|
|
192
|
+
const availableThemes = useMemo((): ThemePreviewItem[] => {
|
|
193
|
+
const fromSettings = settings.appearance?.availableThemes;
|
|
194
|
+
if (fromSettings?.length) return fromSettings;
|
|
195
|
+
return localThemes;
|
|
196
|
+
}, [settings.appearance?.availableThemes, localThemes]);
|
|
197
|
+
|
|
184
198
|
// Determine if we're in dark mode for preview
|
|
185
199
|
const [isDarkForPreview, setIsDarkForPreview] = useState(() => {
|
|
186
200
|
if (typeof window === 'undefined') return false;
|