@owdproject/react-theme-win95 0.0.0-stage → 4.0.0-alpha.4

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
@@ -1,3 +1,81 @@
1
- # Temporary Holding Version
1
+ # @owdproject/react-theme-win95
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Windows 95 theme for **Open Web Desktop**, built with **React** and
4
+ [React95](https://react95.io).
5
+
6
+ - **Engine**: [`@owdproject/core`](https://github.com/owdproject/core) — headless windows/apps/workspaces.
7
+ - **Bridges**: [`@owdproject/react`](https://github.com/owdproject/core) — `useSyncExternalStore` hooks.
8
+ - **UI**: [`@owdproject/react-kit-react95`](../owd-kit-react95) — [React95](https://react95.io) `@react95/core` integration.
9
+
10
+ This is a **minimal vertical slice**: a working desktop shell (background, draggable/resizable
11
+ React95 windows, task bar, start menu) plus two demo apps. It proves the whole React chain works
12
+ end-to-end on top of the OWD engine.
13
+
14
+ ## Run it
15
+
16
+ ```bash
17
+ # from the repo root (client/)
18
+ pnpm install
19
+ pnpm --filter @owdproject/react-theme-win95 dev
20
+ # open http://localhost:5173
21
+ ```
22
+
23
+ ## Use it
24
+
25
+ ```tsx
26
+ import { createDesktop } from '@owdproject/core'
27
+ import { DesktopShell } from '@owdproject/react-theme-win95'
28
+ import '@owdproject/react-kit-react95/styles.css'
29
+ import '@owdproject/react-theme-win95/styles.css'
30
+
31
+ const desktop = createDesktop()
32
+
33
+ export default function App() {
34
+ return <DesktopShell desktop={desktop} watermark="React win95" />
35
+ }
36
+ ```
37
+
38
+ ### Custom apps
39
+
40
+ ```tsx
41
+ import type { DesktopApp } from '@owdproject/react-theme-win95'
42
+ import { Notepad } from '@react95/icons'
43
+
44
+ const apps: DesktopApp[] = [
45
+ {
46
+ id: 'hello',
47
+ title: 'Hello',
48
+ icon: <Notepad variant="16x16_4" />,
49
+ defaultSize: { width: 420, height: 300 },
50
+ component: ({ window, desktop }) => (
51
+ <div style={{ padding: 8 }}>
52
+ <p>Window {window.id}</p>
53
+ <button onClick={() => desktop.windows.closeWindow(window.id)}>Close</button>
54
+ </div>
55
+ ),
56
+ },
57
+ ]
58
+ ```
59
+
60
+ ## Architecture
61
+
62
+ | File | Role |
63
+ | --- | --- |
64
+ | `src/DesktopShell.tsx` | Root shell: workarea, background, windows, start menu, task bar. |
65
+ | `src/components/DesktopWindow.tsx` | One window. Reads state from `useWindowInstance`, moves/resizes via `useDesktopDrag` / `useDesktopResize`. Commits geometry back to the core on release. |
66
+ | `src/components/SystemBar.tsx` | Task bar (React95 `Frame` + `Button`), driven by `useWindowManager`. |
67
+ | `src/components/StartMenuPanel.tsx` | Start menu (React95 `List`). |
68
+ | `src/apps/*` | Demo apps (`About`, `Notepad`). |
69
+ | `src/theme.css` | Layout + app styles (visual primitives come from the kit). |
70
+ | `playground/` | Vite + React playground app. |
71
+
72
+ `@owdproject/core` is the single source of truth for window geometry and focus; React95 only
73
+ renders the chrome, and `@owdproject/react` bridges the immutable core snapshots to React.
74
+
75
+ > ℹ️ The playground Vite config includes a `build.rollupOptions.treeshake.moduleSideEffects`
76
+ > rule for `@react95/*` (they ship no `sideEffects: false`), otherwise all 1000+ React95 icons
77
+ > get bundled. Copy it into your own app's Vite config.
78
+
79
+ ## License
80
+
81
+ MIT
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@owdproject/react-theme-win95",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "4.0.0-alpha.4",
4
+ "description": "Windows 95 theme for Open Web Desktop built with React, @owdproject/core and @owdproject/react.",
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "Diego Villa",
8
+ "url": "https://github.com/dxlliv"
9
+ },
10
+ "type": "module",
11
+ "main": "./src/index.ts",
12
+ "module": "./src/index.ts",
13
+ "types": "./src/index.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./src/index.ts",
17
+ "import": "./src/index.ts",
18
+ "default": "./src/index.ts"
19
+ },
20
+ "./styles.css": "./src/theme.css",
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": [
24
+ "src"
25
+ ],
26
+ "scripts": {
27
+ "dev": "vite --config playground/vite.config.ts",
28
+ "build": "vite build --config playground/vite.config.ts",
29
+ "preview": "vite preview --config playground/vite.config.ts",
30
+ "typecheck": "tsc --noEmit -p tsconfig.json"
31
+ },
32
+ "dependencies": {
33
+ "@owdproject/core": "^4.0.0-alpha.3",
34
+ "@owdproject/react-kit-react95": "^4.0.0-alpha.3",
35
+ "@owdproject/react": "^4.0.0-alpha.3",
36
+ "@react95/icons": "^2.8.0"
37
+ },
38
+ "peerDependencies": {
39
+ "react": ">=18.0.0",
40
+ "react-dom": ">=18.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/react": "^19.0.0",
44
+ "@types/react-dom": "^19.0.0",
45
+ "@vitejs/plugin-react": "^4.3.4",
46
+ "react": "^19.0.0",
47
+ "react-dom": "^19.0.0",
48
+ "typescript": "~6.0.3",
49
+ "vite": "^6.0.0"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public",
53
+ "tag": "alpha",
54
+ "registry": "https://registry.npmjs.org/"
55
+ }
56
+ }
@@ -0,0 +1,84 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
2
+ import type { Desktop } from '@owdproject/core'
3
+ import { useWindowManager } from '@owdproject/react'
4
+ import { React95Provider } from '@owdproject/react-kit-react95'
5
+ import { DesktopRuntimeContext } from './desktop-context'
6
+ import { DesktopWindow } from './components/DesktopWindow'
7
+ import { SystemBar } from './components/SystemBar'
8
+ import { StartMenuPanel } from './components/StartMenuPanel'
9
+ import { registerDesktopApps } from './applications'
10
+ import type { DesktopApp } from './types'
11
+
12
+ const SYSTEMBAR_HEIGHT = 28
13
+ const EMPTY_APPS: DesktopApp[] = []
14
+
15
+ export interface DesktopShellProps {
16
+ /** A `@owdproject/core` Desktop instance. */
17
+ desktop: Desktop
18
+ /** Menu entries; components/configs register with the engine on mount. */
19
+ apps?: DesktopApp[]
20
+ /** Centered desktop watermark text. */
21
+ watermark?: ReactNode
22
+ }
23
+
24
+ /**
25
+ * Root Windows 95 desktop shell for React.
26
+ * Composes `@owdproject/react-kit-react95` chrome with `@owdproject/react` hooks
27
+ * and the headless `@owdproject/core` engine.
28
+ */
29
+ export function DesktopShell({ desktop, apps = EMPTY_APPS, watermark = 'Open Web Desktop' }: DesktopShellProps) {
30
+ const rootRef = useRef<HTMLDivElement>(null)
31
+ const [startOpen, setStartOpen] = useState(false)
32
+ const { windows } = useWindowManager(desktop.windows)
33
+
34
+ useEffect(() => {
35
+ registerDesktopApps(desktop, apps)
36
+ }, [desktop, apps])
37
+
38
+ const getWorkarea = useCallback(() => {
39
+ const el = rootRef.current
40
+ const width = el?.clientWidth ?? (typeof window !== 'undefined' ? window.innerWidth : 1024)
41
+ const height = (el?.clientHeight ?? (typeof window !== 'undefined' ? window.innerHeight : 768)) - SYSTEMBAR_HEIGHT
42
+ return { x: 0, y: 0, width, height }
43
+ }, [])
44
+
45
+ useEffect(() => {
46
+ const sync = () => desktop.workspaces.setWorkarea(getWorkarea())
47
+ sync()
48
+ window.addEventListener('resize', sync)
49
+ return () => window.removeEventListener('resize', sync)
50
+ }, [desktop, getWorkarea])
51
+
52
+ const runtime = useMemo(() => ({ desktop, getWorkarea }), [desktop, getWorkarea])
53
+
54
+ const launch = (app: DesktopApp) => {
55
+ void desktop.applications.launch(app.id, app.entryKey, app.argument)
56
+ setStartOpen(false)
57
+ }
58
+
59
+ const activeWindows = windows.filter((win) => win.state.active !== false)
60
+ const appById = new Map(apps.map((app) => [app.id, app]))
61
+
62
+ return (
63
+ <DesktopRuntimeContext.Provider value={runtime}>
64
+ <React95Provider>
65
+ <div ref={rootRef} className="owd-shell" onPointerDown={() => setStartOpen(false)}>
66
+ <div className="owd-shell__desktop r95-owd-desktop">
67
+ {watermark ? <div className="owd-shell__watermark">{watermark}</div> : null}
68
+ {activeWindows.map((win) => (
69
+ <DesktopWindow key={win.id} window={win} app={appById.get(win.applicationId ?? '')} />
70
+ ))}
71
+ </div>
72
+
73
+ {startOpen ? (
74
+ <div className="owd-shell__startmenu" onPointerDown={(event) => event.stopPropagation()}>
75
+ <StartMenuPanel apps={apps} onLaunch={launch} />
76
+ </div>
77
+ ) : null}
78
+
79
+ <SystemBar apps={apps} startActive={startOpen} onStartClick={() => setStartOpen((open) => !open)} />
80
+ </div>
81
+ </React95Provider>
82
+ </DesktopRuntimeContext.Provider>
83
+ )
84
+ }
@@ -0,0 +1,25 @@
1
+ import type { Desktop } from '@owdproject/core'
2
+ import { registerReactApplication } from '@owdproject/react'
3
+ import type { DesktopApp } from './types'
4
+
5
+ /** Register shorthand apps once. Existing registrations remain owned by the consumer. */
6
+ export function registerDesktopApps(desktop: Desktop, apps: readonly DesktopApp[]): void {
7
+ for (const app of apps) {
8
+ if (desktop.applications.get(app.id) || (!app.component && !app.config)) continue
9
+ const { entries = {}, ...config } = app.config ?? {}
10
+ registerReactApplication(desktop, {
11
+ ...config,
12
+ id: app.id,
13
+ title: app.title,
14
+ entries,
15
+ windows: config.windows ?? (app.component ? {
16
+ main: {
17
+ title: app.title,
18
+ size: app.defaultSize ?? { width: 480, height: 340 },
19
+ position: { x: 48, y: 40 },
20
+ component: app.component,
21
+ },
22
+ } : undefined),
23
+ })
24
+ }
25
+ }
@@ -0,0 +1,134 @@
1
+ import { useReducer, useRef, type CSSProperties } from 'react'
2
+ import type { ResizeDirection, WindowBounds, WindowInstance, WindowState } from '@owdproject/core'
3
+ import { closeReactApplicationWindow, ReactApplicationWindow, useDesktopDrag, useDesktopResize, useWindowInstance } from '@owdproject/react'
4
+ import { OwdWindow } from '@owdproject/react-kit-react95'
5
+ import { useDesktopRuntime } from '../desktop-context'
6
+ import type { DesktopApp } from '../types'
7
+
8
+ const FALLBACK_SIZE = { width: 480, height: 340 }
9
+
10
+ function readBounds(state: WindowState, fallback: { width: number; height: number }): WindowBounds {
11
+ const pos = state.position ?? {}
12
+ const size = state.size ?? {}
13
+ return {
14
+ x: pos.x ?? 0,
15
+ y: pos.y ?? 0,
16
+ width: typeof size.width === 'number' ? size.width : fallback.width,
17
+ height: typeof size.height === 'number' ? size.height : fallback.height,
18
+ }
19
+ }
20
+
21
+ export interface DesktopWindowProps {
22
+ window: WindowInstance
23
+ app?: DesktopApp
24
+ }
25
+
26
+ /**
27
+ * A single desktop window: reads live state from `@owdproject/react`'s
28
+ * `useWindowInstance`, drives move/resize through `@owdproject/core`'s pointer
29
+ * sessions (`useDesktopDrag` / `useDesktopResize`) and renders React95 window
30
+ * chrome via the kit's `OwdWindow`.
31
+ *
32
+ * While dragging, geometry is kept in local state for smoothness and committed
33
+ * back to the core engine on release.
34
+ */
35
+ export function DesktopWindow({ window: instance, app }: DesktopWindowProps) {
36
+ const { desktop, getWorkarea } = useDesktopRuntime()
37
+ const { state } = useWindowInstance(instance)
38
+ const fallback = app?.defaultSize ?? FALLBACK_SIZE
39
+
40
+ const liveBounds = useRef<WindowBounds | null>(null)
41
+ const [, forceRender] = useReducer((n: number) => n + 1, 0)
42
+
43
+ const commit = (bounds: WindowBounds) => {
44
+ liveBounds.current = null
45
+ instance.setPosition({ x: bounds.x, y: bounds.y })
46
+ instance.setSize({ width: bounds.width, height: bounds.height })
47
+ forceRender()
48
+ }
49
+
50
+ const { startDrag } = useDesktopDrag({
51
+ getBounds: () => readBounds(instance.getSnapshot(), fallback),
52
+ getWorkarea,
53
+ onMove: (bounds) => {
54
+ liveBounds.current = bounds
55
+ forceRender()
56
+ },
57
+ onEnd: commit,
58
+ onCancel: () => {
59
+ liveBounds.current = null
60
+ forceRender()
61
+ },
62
+ })
63
+
64
+ const { startResize } = useDesktopResize({
65
+ getBounds: () => readBounds(instance.getSnapshot(), fallback),
66
+ minWidth: 180,
67
+ minHeight: 120,
68
+ onMove: (bounds) => {
69
+ liveBounds.current = bounds
70
+ forceRender()
71
+ },
72
+ onEnd: commit,
73
+ onCancel: () => {
74
+ liveBounds.current = null
75
+ forceRender()
76
+ },
77
+ })
78
+
79
+ const bounds = liveBounds.current ?? readBounds(state, fallback)
80
+ const maximized = state.maximized === true
81
+
82
+ const style: CSSProperties = {
83
+ left: bounds.x,
84
+ top: bounds.y,
85
+ width: bounds.width,
86
+ height: bounds.height,
87
+ zIndex: state.position?.z ?? 0,
88
+ }
89
+
90
+ const focus = () => desktop.windows.focusWindow(instance.id)
91
+
92
+ return (
93
+ <OwdWindow
94
+ title={instance.config.title ?? app?.title ?? instance.id}
95
+ icon={app?.icon}
96
+ active={state.focused}
97
+ maximized={maximized}
98
+ resizable={state.resizable !== false}
99
+ minimizable={state.minimizable !== false}
100
+ maximizable={state.maximizable !== false}
101
+ closable={state.destroyable !== false}
102
+ style={style}
103
+ onPointerDownCapture={focus}
104
+ onTitlePointerDown={(event) => {
105
+ if (event.button !== 0) return
106
+ // Capturing pointers from title-bar controls prevents their click action.
107
+ if (event.target instanceof Element && event.target.closest('button, [role="button"]')) return
108
+ focus()
109
+ startDrag(event.nativeEvent)
110
+ }}
111
+ onTitleDoubleClick={() => {
112
+ if (state.maximizable === false) return
113
+ instance.setLayout(maximized ? 'normal' : 'maximize', getWorkarea())
114
+ }}
115
+ onResizePointerDown={(event, direction) => {
116
+ if (event.button !== 0) return
117
+ focus()
118
+ startResize(event.nativeEvent, direction as ResizeDirection)
119
+ }}
120
+ onClose={() => { void closeReactApplicationWindow(desktop, instance) }}
121
+ onMinimize={() => instance.minimize()}
122
+ onToggleMaximize={() => {
123
+ if (state.maximizable === false) return
124
+ instance.setLayout(maximized ? 'normal' : 'maximize', getWorkarea())
125
+ }}
126
+ >
127
+ {instance.config.component ? (
128
+ <ReactApplicationWindow window={instance} desktop={desktop} />
129
+ ) : (
130
+ <div className="owd-window__empty">No component registered for “{instance.model}”.</div>
131
+ )}
132
+ </OwdWindow>
133
+ )
134
+ }
@@ -0,0 +1,32 @@
1
+ import { PowerOff } from '@react95/icons'
2
+ import { List } from '@owdproject/react-kit-react95'
3
+ import type { DesktopApp } from '../types'
4
+
5
+ export interface StartMenuPanelProps {
6
+ apps: DesktopApp[]
7
+ onLaunch: (app: DesktopApp) => void
8
+ }
9
+
10
+ /** Start menu built with React95 `List` + `List.Item`. */
11
+ export function StartMenuPanel({ apps, onLaunch }: StartMenuPanelProps) {
12
+ return (
13
+ <div className="owd-startmenu">
14
+ <div className="owd-startmenu__rail">
15
+ <span>
16
+ Open Web <strong>Desktop</strong>
17
+ </span>
18
+ </div>
19
+ <List className="owd-startmenu__list">
20
+ {apps.map((app) => (
21
+ <List.Item key={app.id} icon={app.icon} onClick={() => onLaunch(app)}>
22
+ {app.title}
23
+ </List.Item>
24
+ ))}
25
+ <List.Divider />
26
+ <List.Item icon={<PowerOff variant="16x16_4" />} disabled>
27
+ Shut Down…
28
+ </List.Item>
29
+ </List>
30
+ </div>
31
+ )
32
+ }
@@ -0,0 +1,73 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { Logo } from '@react95/icons'
3
+ import { Button, Frame } from '@owdproject/react-kit-react95'
4
+ import { useWindowManager } from '@owdproject/react'
5
+ import { useDesktopRuntime } from '../desktop-context'
6
+ import type { DesktopApp } from '../types'
7
+
8
+ function useClock() {
9
+ const [now, setNow] = useState(() => new Date())
10
+ useEffect(() => {
11
+ const id = setInterval(() => setNow(new Date()), 10_000)
12
+ return () => clearInterval(id)
13
+ }, [])
14
+ return now
15
+ }
16
+
17
+ export interface SystemBarProps {
18
+ apps: DesktopApp[]
19
+ onStartClick: () => void
20
+ startActive: boolean
21
+ }
22
+
23
+ /**
24
+ * Bottom system bar built from React95 `Frame` + `Button` primitives.
25
+ * Unlike React95's own `TaskBar`, it is driven by the OWD window manager
26
+ * (`useWindowManager`), so it lists real `@owdproject/core` windows.
27
+ */
28
+ export function SystemBar({ apps, onStartClick, startActive }: SystemBarProps) {
29
+ const { desktop } = useDesktopRuntime()
30
+ const { windows, focused } = useWindowManager(desktop.windows)
31
+ const now = useClock()
32
+
33
+ const appById = new Map(apps.map((app) => [app.id, app]))
34
+ const clock = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
35
+
36
+ return (
37
+ <Frame className="owd-taskbar">
38
+ <Button
39
+ className={`owd-taskbar__start${startActive ? ' is-active' : ''}`}
40
+ onClick={onStartClick}
41
+ aria-pressed={startActive}
42
+ >
43
+ <Logo variant="16x16_4" />
44
+ <span className="owd-taskbar__start-label">Start</span>
45
+ </Button>
46
+
47
+ <Frame className="owd-taskbar__tasks">
48
+ {windows.map((win) => {
49
+ const app = appById.get(win.applicationId ?? '')
50
+ const title = win.config.title ?? app?.title ?? win.id
51
+ const isActive = focused?.id === win.id
52
+ return (
53
+ <Button
54
+ key={win.id}
55
+ className={`owd-task${isActive ? ' is-active' : ''}`}
56
+ title={title}
57
+ onClick={() => {
58
+ if (win.state.active === false) win.unminimize()
59
+ desktop.windows.focusWindow(win.id)
60
+ }}
61
+ >
62
+ <span className="owd-task__label">{title}</span>
63
+ </Button>
64
+ )
65
+ })}
66
+ </Frame>
67
+
68
+ <Frame className="owd-taskbar__tray">
69
+ <span className="owd-taskbar__clock">{clock}</span>
70
+ </Frame>
71
+ </Frame>
72
+ )
73
+ }
@@ -0,0 +1,18 @@
1
+ import { createContext, useContext } from 'react'
2
+ import type { Desktop, DesktopWorkareaRect } from '@owdproject/core'
3
+
4
+ export interface DesktopRuntime {
5
+ desktop: Desktop
6
+ /** Current usable desktop area (excluding the system bar), in shell coordinates. */
7
+ getWorkarea: () => DesktopWorkareaRect
8
+ }
9
+
10
+ export const DesktopRuntimeContext = createContext<DesktopRuntime | null>(null)
11
+
12
+ export function useDesktopRuntime(): DesktopRuntime {
13
+ const runtime = useContext(DesktopRuntimeContext)
14
+ if (!runtime) {
15
+ throw new Error('[owd-react-theme-win95] useDesktopRuntime() must be used inside <DesktopShell>.')
16
+ }
17
+ return runtime
18
+ }
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @owdproject/react-theme-win95
3
+ * Windows 95 Open Web Desktop theme for React.
4
+ *
5
+ * Built on the headless `@owdproject/core` engine, the `@owdproject/react`
6
+ * hooks and the `@owdproject/react-kit-react95` visual primitives.
7
+ */
8
+
9
+ export { DesktopShell, type DesktopShellProps } from './DesktopShell'
10
+ export { DesktopWindow, type DesktopWindowProps } from './components/DesktopWindow'
11
+ export { SystemBar, type SystemBarProps } from './components/SystemBar'
12
+ export { StartMenuPanel, type StartMenuPanelProps } from './components/StartMenuPanel'
13
+ export { useDesktopRuntime, DesktopRuntimeContext, type DesktopRuntime } from './desktop-context'
14
+ export type { DesktopApp, DesktopAppRenderProps } from './types'
15
+
16
+ // Re-export the kit primitives so consumers get one entry point.
17
+ export * from '@owdproject/react-kit-react95'
package/src/theme.css ADDED
@@ -0,0 +1,221 @@
1
+ /*
2
+ * @owdproject/react-theme-win95 — theme layout & app styles.
3
+ * Visual primitives come from @react95/core (via @owdproject/react-kit-react95).
4
+ */
5
+
6
+ /* The theme owns its kit: loading the theme styles also loads the kit's OWD
7
+ window glue, so consumers only import this single stylesheet. */
8
+ @import '@owdproject/react-kit-react95/styles.css';
9
+
10
+ .owd-shell {
11
+ position: fixed;
12
+ inset: 0;
13
+ overflow: hidden;
14
+ }
15
+
16
+ .owd-shell__desktop {
17
+ position: absolute;
18
+ inset: 0 0 28px 0;
19
+ overflow: hidden;
20
+ }
21
+
22
+ .owd-shell__watermark {
23
+ position: absolute;
24
+ right: 16px;
25
+ bottom: 16px;
26
+ color: rgba(255, 255, 255, 0.6);
27
+ font-weight: bold;
28
+ text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.35);
29
+ pointer-events: none;
30
+ }
31
+
32
+ .owd-shell__startmenu {
33
+ position: absolute;
34
+ left: 2px;
35
+ bottom: 28px;
36
+ z-index: 100000;
37
+ }
38
+
39
+ /* ----------------------------------------------------------- System bar */
40
+
41
+ .owd-taskbar {
42
+ position: fixed;
43
+ left: 0;
44
+ right: 0;
45
+ bottom: 0;
46
+ height: 28px;
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 4px;
50
+ padding: 2px;
51
+ z-index: 90000;
52
+ background: var(--r95-color-material, #c3c7cb);
53
+ box-shadow:
54
+ inset 1px 1px 0 var(--r95-color-borderLightest, #fff),
55
+ inset -1px -1px 0 var(--r95-color-borderDark, #868a8e);
56
+ }
57
+
58
+ .owd-taskbar__start {
59
+ display: inline-flex;
60
+ align-items: center;
61
+ gap: 4px;
62
+ font-weight: bold;
63
+ flex: 0 0 auto;
64
+ }
65
+
66
+ .owd-taskbar__start.is-active {
67
+ padding-top: 2px;
68
+ padding-left: 2px;
69
+ }
70
+
71
+ .owd-taskbar__start-label {
72
+ line-height: 1;
73
+ }
74
+
75
+ .owd-taskbar__tasks {
76
+ flex: 1 1 auto;
77
+ min-width: 0;
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 3px;
81
+ background: transparent;
82
+ box-shadow: none;
83
+ overflow: hidden;
84
+ }
85
+
86
+ .owd-task {
87
+ display: inline-flex;
88
+ align-items: center;
89
+ min-width: 110px;
90
+ max-width: 168px;
91
+ overflow: hidden;
92
+ }
93
+
94
+ .owd-task.is-active {
95
+ box-shadow:
96
+ inset 1px 1px 0 var(--r95-color-borderDark, #868a8e),
97
+ inset -1px -1px 0 var(--r95-color-borderLightest, #fff);
98
+ background-color: var(--r95-color-borderLight, #d2d2d2);
99
+ }
100
+
101
+ .owd-task__label {
102
+ overflow: hidden;
103
+ text-overflow: ellipsis;
104
+ white-space: nowrap;
105
+ }
106
+
107
+ .owd-taskbar__tray {
108
+ flex: 0 0 auto;
109
+ display: flex;
110
+ align-items: center;
111
+ padding: 0 6px;
112
+ background: transparent;
113
+ box-shadow: none;
114
+ }
115
+
116
+ .owd-taskbar__clock {
117
+ padding: 0 2px;
118
+ }
119
+
120
+ /* ------------------------------------------------------------ Start menu */
121
+
122
+ .owd-startmenu {
123
+ display: flex;
124
+ background: var(--r95-color-material, #c3c7cb);
125
+ border: 2px solid;
126
+ border-color: var(--r95-color-borderLight) var(--r95-color-borderDarkest)
127
+ var(--r95-color-borderDarkest) var(--r95-color-borderLight);
128
+ box-shadow: inset 1px 1px 0 var(--r95-color-borderLightest);
129
+ }
130
+
131
+ .owd-startmenu__rail {
132
+ width: 26px;
133
+ background: linear-gradient(180deg, #000e7a, #1084d0);
134
+ color: #fff;
135
+ writing-mode: vertical-rl;
136
+ transform: rotate(180deg);
137
+ display: flex;
138
+ align-items: flex-end;
139
+ justify-content: center;
140
+ padding: 8px 0;
141
+ font-weight: bold;
142
+ letter-spacing: 1px;
143
+ }
144
+
145
+ .owd-startmenu__list {
146
+ border: 0;
147
+ box-shadow: none;
148
+ background: transparent;
149
+ min-width: 190px;
150
+ }
151
+
152
+ /* ------------------------------------------------------- App: About box */
153
+
154
+ .owd-about {
155
+ display: flex;
156
+ flex-direction: column;
157
+ gap: 8px;
158
+ height: 100%;
159
+ padding: 4px;
160
+ user-select: text;
161
+ }
162
+
163
+ .owd-about__banner {
164
+ display: flex;
165
+ align-items: center;
166
+ gap: 10px;
167
+ }
168
+
169
+ .owd-about__small {
170
+ color: #404040;
171
+ }
172
+
173
+ .owd-about__divider {
174
+ width: 100%;
175
+ border: 0;
176
+ border-top: 1px solid var(--r95-color-borderDark);
177
+ border-bottom: 1px solid var(--r95-color-borderLight);
178
+ }
179
+
180
+ .owd-about__text {
181
+ margin: 0;
182
+ line-height: 1.5;
183
+ }
184
+
185
+ .owd-about__actions {
186
+ margin-top: auto;
187
+ display: flex;
188
+ justify-content: flex-end;
189
+ }
190
+
191
+ /* --------------------------------------------------------- App: Notepad */
192
+
193
+ .owd-notepad {
194
+ display: flex;
195
+ flex-direction: column;
196
+ height: 100%;
197
+ }
198
+
199
+ .owd-notepad__menu {
200
+ display: flex;
201
+ gap: 14px;
202
+ padding: 2px 6px;
203
+ }
204
+
205
+ .owd-notepad__area {
206
+ flex: 1 1 auto;
207
+ width: 100%;
208
+ resize: none;
209
+ border: 0;
210
+ outline: none;
211
+ padding: 4px;
212
+ background: #fff;
213
+ font-family: 'Lucida Console', 'Courier New', monospace;
214
+ font-size: 12px;
215
+ line-height: 1.4;
216
+ }
217
+
218
+ .owd-window__empty {
219
+ padding: 8px;
220
+ color: #404040;
221
+ }
package/src/types.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type { ComponentType, ReactElement } from 'react'
2
+ import type { ReactApplicationConfig, ReactApplicationWindowProps } from '@owdproject/react'
3
+
4
+ /** Props passed to every application window component. */
5
+ export type DesktopAppRenderProps = ReactApplicationWindowProps
6
+
7
+ /** A React application that can be launched from the start menu / task bar. */
8
+ export interface DesktopApp {
9
+ id: string
10
+ title: string
11
+ /** React95 icon used in the start menu, window title bar and task bar. */
12
+ icon?: ReactElement
13
+ defaultSize?: { width: number; height: number }
14
+ /** Optional shorthand for a single `main` window. Registered apps need no component here. */
15
+ component?: ComponentType<DesktopAppRenderProps>
16
+ /** Engine options, including entries, models, singleton and lifecycle callbacks. */
17
+ config?: Omit<ReactApplicationConfig, 'id' | 'title' | 'entries'> & {
18
+ entries?: ReactApplicationConfig['entries']
19
+ }
20
+ entryKey?: string
21
+ argument?: string
22
+ }