ssovee-os-web-ui 0.1.25 → 0.1.28

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
@@ -6,18 +6,13 @@ Reusable React UI components for building shared desktop-style web experiences.
6
6
 
7
7
  This package provides a shared set of React UI components for SSOVEE applications. It includes themed UI primitives, hooks, and desktop-style window wrappers for app-like layouts.
8
8
 
9
- ## What’s new in v0.1.24
10
-
11
- - Added window container components for app-style layouts
12
- - Documented usage examples for both window variants
13
- - Improved package exports for easier consumption
14
-
15
9
  ## Features
16
10
 
17
11
  - Ready-to-use React UI components
18
12
  - Packaged theme stylesheet for shared visual tokens
19
13
  - TypeScript support for the main public components
20
14
  - Window components for desktop-style app shells
15
+ - Plugin playground support for local plugin testing
21
16
 
22
17
  ## Install
23
18
 
@@ -64,6 +59,7 @@ export function Example() {
64
59
 
65
60
  ### Simple window
66
61
 
62
+ {% raw %}
67
63
  ```tsx
68
64
  import { WindowWithoutSideMenu, Typography } from "ssovee-os-web-ui";
69
65
 
@@ -86,9 +82,11 @@ export function NotesWindow() {
86
82
  );
87
83
  }
88
84
  ```
85
+ {% endraw %}
89
86
 
90
87
  ### Window with sidebar menu
91
88
 
89
+ {% raw %}
92
90
  ```tsx
93
91
  import { WindowWithSideMenu, Typography, Button } from "ssovee-os-web-ui";
94
92
 
@@ -123,6 +121,35 @@ export function SettingsWindow() {
123
121
  );
124
122
  }
125
123
  ```
124
+ {% endraw %}
125
+
126
+ ## Plugin playground
127
+
128
+ ```tsx
129
+ import { PluginPlayground, Typography, Button } from "ssovee-os-web-ui";
130
+
131
+ const pluginMetadata = {
132
+ title: "Plugin Preview",
133
+ defaultWidth: 720,
134
+ defaultHeight: 520,
135
+ windowVariant: "with-side-menu",
136
+ };
137
+
138
+ export function PluginPreview() {
139
+ return (
140
+ <PluginPlayground
141
+ metadata={pluginMetadata}
142
+ onClose={() => console.log("Playground closed")}
143
+ onMinimize={(isMinimized) => console.log("Minimized:", isMinimized)}
144
+ >
145
+ <div>
146
+ <Typography variant="h6">Plugin Content</Typography>
147
+ <Button variant="primary">Run</Button>
148
+ </div>
149
+ </PluginPlayground>
150
+ );
151
+ }
152
+ ```
126
153
 
127
154
  ## Hooks
128
155
 
@@ -163,6 +190,7 @@ The package exports these components and types for external use:
163
190
  - `Tabs`
164
191
  - `Typography`
165
192
  - `ImageWithPlaceholder`
193
+ - `PluginPlayground`
166
194
  - `WindowWithSideMenu`
167
195
  - `WindowWithoutSideMenu`
168
196
  - `Table` and its subcomponents
package/dist/index.cjs CHANGED
@@ -2036,6 +2036,201 @@ var Tabs = ({
2036
2036
  ] });
2037
2037
  };
2038
2038
  var Tabs_default = Tabs;
2039
+ var DEFAULT_BACKGROUND = "/wallpaper/Wallpaper3.webp";
2040
+ var DEFAULT_MOCK_USER = {
2041
+ id: "dev-user",
2042
+ name: "Plugin Developer",
2043
+ email: "dev@local.test"
2044
+ };
2045
+ var DEFAULT_TOKEN = "playground-dev-token";
2046
+ function renderPluginChild(child, app, api, reloadKey) {
2047
+ if (!child) return null;
2048
+ if (typeof child === "function") {
2049
+ const renderFn = child;
2050
+ return renderFn({ app, api });
2051
+ }
2052
+ return React22.Children.map(child, (node, index) => {
2053
+ if (!React22.isValidElement(node)) {
2054
+ return node;
2055
+ }
2056
+ return React22.cloneElement(
2057
+ node,
2058
+ {
2059
+ key: `${reloadKey}-${index}`,
2060
+ app,
2061
+ api
2062
+ }
2063
+ );
2064
+ });
2065
+ }
2066
+ function getDefaultSize(metadata) {
2067
+ const manifestWindowSize = metadata.windowSize?.windowSize;
2068
+ const minWidth = metadata.minWidth ?? 520;
2069
+ const minHeight = metadata.minHeight ?? 360;
2070
+ return {
2071
+ width: Math.max(
2072
+ manifestWindowSize?.width ?? metadata.defaultWidth ?? 960,
2073
+ minWidth
2074
+ ),
2075
+ height: Math.max(
2076
+ manifestWindowSize?.height ?? metadata.defaultHeight ?? 680,
2077
+ minHeight
2078
+ )
2079
+ };
2080
+ }
2081
+ function PluginPlayground({
2082
+ children,
2083
+ metadata,
2084
+ backgroundImage,
2085
+ onClose,
2086
+ onMinimize
2087
+ }) {
2088
+ const [mockUser] = React22.useState(DEFAULT_MOCK_USER);
2089
+ const [mockToken] = React22.useState(DEFAULT_TOKEN);
2090
+ const [isMinimized, setIsMinimized] = React22.useState(false);
2091
+ const [isClosed, setIsClosed] = React22.useState(false);
2092
+ const [isActive, setIsActive] = React22.useState(true);
2093
+ const [isNightMode, setIsNightMode] = React22.useState(true);
2094
+ React22.useEffect(() => {
2095
+ if (typeof window === "undefined") return;
2096
+ const storedTheme = window.localStorage.getItem("plugin-playground-theme");
2097
+ const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
2098
+ const shouldUseDark = storedTheme ? storedTheme === "dark" : prefersDark;
2099
+ setIsNightMode(shouldUseDark);
2100
+ }, []);
2101
+ React22.useEffect(() => {
2102
+ if (typeof document === "undefined") return;
2103
+ document.documentElement.classList.toggle("dark", isNightMode);
2104
+ window.localStorage.setItem(
2105
+ "plugin-playground-theme",
2106
+ isNightMode ? "dark" : "light"
2107
+ );
2108
+ }, [isNightMode]);
2109
+ const title = metadata.title ?? metadata.name ?? "Plugin Playground";
2110
+ const slug = metadata.slug ?? "local-plugin";
2111
+ const defaultSize = React22.useMemo(() => getDefaultSize(metadata), [metadata]);
2112
+ const canvasBackground = backgroundImage ?? metadata.backgroundImage ?? DEFAULT_BACKGROUND;
2113
+ const isWindowResizable = metadata.windowSize?.isAppWindowResizing ?? metadata.isResizing ?? true;
2114
+ const minimizeWindow = React22.useCallback(() => {
2115
+ setIsActive(false);
2116
+ setIsMinimized(true);
2117
+ setIsClosed(false);
2118
+ onMinimize?.(true);
2119
+ }, [onMinimize]);
2120
+ const closeWindow = React22.useCallback(() => {
2121
+ setIsActive(false);
2122
+ setIsClosed(true);
2123
+ setIsMinimized(false);
2124
+ onClose?.();
2125
+ }, [onClose]);
2126
+ const restoreWindow = React22.useCallback(() => {
2127
+ setIsActive(true);
2128
+ setIsClosed(false);
2129
+ setIsMinimized(false);
2130
+ onMinimize?.(false);
2131
+ }, [onMinimize]);
2132
+ const mockApi = React22.useMemo(
2133
+ () => ({
2134
+ showToast: (message, type) => {
2135
+ console.info(`[playground:${type}] ${message}`);
2136
+ },
2137
+ openApp: (app) => {
2138
+ console.info(`[playground:openApp] ${app.slug}`);
2139
+ },
2140
+ getUser: async () => {
2141
+ return mockUser;
2142
+ },
2143
+ getToken: async () => {
2144
+ return mockToken;
2145
+ }
2146
+ }),
2147
+ [mockToken, mockUser]
2148
+ );
2149
+ const appProps = React22.useMemo(
2150
+ () => ({
2151
+ name: title,
2152
+ slug,
2153
+ icon: metadata.icon,
2154
+ isActive,
2155
+ isMinimize: isMinimized,
2156
+ componentKey: "plugin-playground",
2157
+ windowSize: {
2158
+ windowSize: defaultSize,
2159
+ isAppWindowResizing: isWindowResizable
2160
+ },
2161
+ metadata: {
2162
+ title,
2163
+ description: metadata.description,
2164
+ ogImage: metadata.icon,
2165
+ keywords: metadata.keywords?.join(", "),
2166
+ author: metadata.author,
2167
+ siteName: "Plugin Playground"
2168
+ },
2169
+ callback: (value) => {
2170
+ if (value === "MINIMIZE_APP") {
2171
+ minimizeWindow();
2172
+ return;
2173
+ }
2174
+ if (value === "CLOSE_APP") {
2175
+ closeWindow();
2176
+ return;
2177
+ }
2178
+ if (value === "CLICKED") {
2179
+ setIsActive(true);
2180
+ }
2181
+ }
2182
+ }),
2183
+ [
2184
+ closeWindow,
2185
+ defaultSize,
2186
+ isActive,
2187
+ isMinimized,
2188
+ metadata.author,
2189
+ metadata.description,
2190
+ metadata.icon,
2191
+ metadata.keywords,
2192
+ minimizeWindow,
2193
+ isWindowResizable,
2194
+ slug,
2195
+ title
2196
+ ]
2197
+ );
2198
+ const injectedPlugin = renderPluginChild(
2199
+ children,
2200
+ appProps,
2201
+ mockApi,
2202
+ Number(isClosed)
2203
+ );
2204
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2205
+ "div",
2206
+ {
2207
+ className: "relative h-screen w-screen overflow-hidden bg-cover bg-center bg-no-repeat text-white transition-colors duration-200 dark:text-slate-100",
2208
+ style: { backgroundImage: `url(${canvasBackground})` },
2209
+ children: [
2210
+ /* @__PURE__ */ jsxRuntime.jsx(
2211
+ "button",
2212
+ {
2213
+ "aria-pressed": isNightMode,
2214
+ className: "absolute left-4 top-4 z-40 rounded-full border border-white/30 bg-white/75 px-3 py-2 text-xs font-medium text-slate-900 shadow-lg shadow-black/20 backdrop-blur-md transition-all duration-200 dark:border-slate-700/60 dark:bg-slate-900/70 dark:text-slate-100 dark:shadow-slate-950/40",
2215
+ onClick: () => setIsNightMode((prev) => !prev),
2216
+ type: "button",
2217
+ children: isNightMode ? "\u2600\uFE0F Light Mood" : "\u{1F319} Night Mood"
2218
+ }
2219
+ ),
2220
+ !isClosed ? injectedPlugin : null,
2221
+ isClosed || isMinimized ? /* @__PURE__ */ jsxRuntime.jsx(
2222
+ "button",
2223
+ {
2224
+ className: "absolute bottom-6 left-1/2 z-40 -translate-x-1/2 rounded-full border border-white/30 bg-black/55 px-4 py-2 text-xs text-white backdrop-blur-md transition-colors duration-200 dark:border-white/20 dark:bg-slate-950/70 dark:text-slate-100",
2225
+ onClick: restoreWindow,
2226
+ type: "button",
2227
+ children: isClosed ? `Reopen ${title}` : `Restore ${title}`
2228
+ }
2229
+ ) : null
2230
+ ]
2231
+ }
2232
+ );
2233
+ }
2039
2234
  var SIDEBAR_WIDTH = 200;
2040
2235
  var initialState = {
2041
2236
  position: { x: 0, y: 40 },
@@ -2928,6 +3123,7 @@ exports.Input = Input_default;
2928
3123
  exports.Loading = Loading_default;
2929
3124
  exports.Modal = Modal_default;
2930
3125
  exports.Pagination = Pagination_default;
3126
+ exports.PluginPlayground = PluginPlayground;
2931
3127
  exports.Progress = Progress_default;
2932
3128
  exports.Radio = Radio_default;
2933
3129
  exports.SearchInput = SearchInput_default;