@omerdlw/base-framework 1.0.0 → 1.0.2
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/LICENSE +1 -1
- package/README.md +121 -52
- package/dist/atoms/index.d.ts +0 -1
- package/dist/atoms/index.d.ts.map +1 -1
- package/dist/atoms/index.js +1 -64
- package/dist/atoms/index.js.map +1 -1
- package/dist/error/index.js +22 -25
- package/dist/error/index.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -9
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +97 -540
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/types.d.ts +3 -45
- package/dist/hooks/types.d.ts.map +1 -1
- package/dist/hooks/use-controllable-state.d.ts +1 -1
- package/dist/hooks/use-controllable-state.d.ts.map +1 -1
- package/dist/hooks/use-global-event.d.ts +0 -2
- package/dist/hooks/use-global-event.d.ts.map +1 -1
- package/dist/hooks/use-hotkey.d.ts +1 -1
- package/dist/hooks/use-hotkey.d.ts.map +1 -1
- package/dist/index.js +11283 -11772
- package/dist/index.js.map +1 -1
- package/dist/kernel/adapters.d.ts.map +1 -1
- package/dist/kernel/index.d.ts +1 -0
- package/dist/kernel/index.d.ts.map +1 -1
- package/dist/kernel/index.js +10 -81
- package/dist/kernel/index.js.map +1 -1
- package/dist/kernel/installed.d.ts.map +1 -1
- package/dist/kernel/page-controller.d.ts.map +1 -1
- package/dist/kernel/provider.d.ts.map +1 -1
- package/dist/modules/ambient/constants.d.ts +1 -1
- package/dist/modules/ambient/constants.d.ts.map +1 -1
- package/dist/modules/ambient/index.js +180 -326
- package/dist/modules/ambient/index.js.map +1 -1
- package/dist/modules/ambient/module.d.ts.map +1 -1
- package/dist/modules/background/constants.d.ts +4 -2
- package/dist/modules/background/constants.d.ts.map +1 -1
- package/dist/modules/background/context.d.ts +3 -0
- package/dist/modules/background/context.d.ts.map +1 -1
- package/dist/modules/background/hooks.d.ts +1 -1
- package/dist/modules/background/index.d.ts +2 -2
- package/dist/modules/background/index.d.ts.map +1 -1
- package/dist/modules/background/index.js +1172 -1270
- package/dist/modules/background/index.js.map +1 -1
- package/dist/modules/background/overlay.d.ts +1 -1
- package/dist/modules/background/overlay.d.ts.map +1 -1
- package/dist/modules/background/types.d.ts +1 -1
- package/dist/modules/background/types.d.ts.map +1 -1
- package/dist/modules/context-menu/constants.d.ts +1 -1
- package/dist/modules/context-menu/constants.d.ts.map +1 -1
- package/dist/modules/context-menu/context.d.ts.map +1 -1
- package/dist/modules/context-menu/hooks.d.ts.map +1 -1
- package/dist/modules/context-menu/index.js +133 -238
- package/dist/modules/context-menu/index.js.map +1 -1
- package/dist/modules/controls/constants.d.ts +1 -1
- package/dist/modules/controls/constants.d.ts.map +1 -1
- package/dist/modules/controls/hooks.d.ts +1 -1
- package/dist/modules/controls/index.js +28 -162
- package/dist/modules/controls/index.js.map +1 -1
- package/dist/modules/controls/types.d.ts +1 -1
- package/dist/modules/controls/types.d.ts.map +1 -1
- package/dist/modules/dock/cards.d.ts +2 -2
- package/dist/modules/dock/constants.d.ts +1 -1
- package/dist/modules/dock/constants.d.ts.map +1 -1
- package/dist/modules/dock/context.d.ts +10 -6
- package/dist/modules/dock/context.d.ts.map +1 -1
- package/dist/modules/dock/hooks.d.ts +5 -5
- package/dist/modules/dock/hooks.d.ts.map +1 -1
- package/dist/modules/dock/index.d.ts +1 -1
- package/dist/modules/dock/index.d.ts.map +1 -1
- package/dist/modules/dock/index.js +2060 -2080
- package/dist/modules/dock/index.js.map +1 -1
- package/dist/modules/dock/runtime/dock.d.ts.map +1 -1
- package/dist/modules/dock/types.d.ts +1 -1
- package/dist/modules/dock/types.d.ts.map +1 -1
- package/dist/modules/index.js +10789 -11027
- package/dist/modules/index.js.map +1 -1
- package/dist/modules/loading/constants.d.ts +4 -2
- package/dist/modules/loading/constants.d.ts.map +1 -1
- package/dist/modules/loading/context.d.ts +3 -0
- package/dist/modules/loading/context.d.ts.map +1 -1
- package/dist/modules/loading/hooks.d.ts +1 -1
- package/dist/modules/loading/hooks.d.ts.map +1 -1
- package/dist/modules/loading/index.d.ts +2 -2
- package/dist/modules/loading/index.d.ts.map +1 -1
- package/dist/modules/loading/index.js +118 -222
- package/dist/modules/loading/index.js.map +1 -1
- package/dist/modules/loading/types.d.ts +1 -1
- package/dist/modules/loading/types.d.ts.map +1 -1
- package/dist/modules/media/constants.d.ts +2 -1
- package/dist/modules/media/constants.d.ts.map +1 -1
- package/dist/modules/media/context.d.ts +3 -0
- package/dist/modules/media/context.d.ts.map +1 -1
- package/dist/modules/media/index.d.ts +2 -2
- package/dist/modules/media/index.d.ts.map +1 -1
- package/dist/modules/media/index.js +61 -218
- package/dist/modules/media/index.js.map +1 -1
- package/dist/modules/modal/constants.d.ts +1 -1
- package/dist/modules/modal/constants.d.ts.map +1 -1
- package/dist/modules/modal/context.d.ts.map +1 -1
- package/dist/modules/modal/hooks.d.ts +2 -2
- package/dist/modules/modal/index.js +20 -366
- package/dist/modules/modal/index.js.map +1 -1
- package/dist/modules/notification/constants.d.ts +4 -2
- package/dist/modules/notification/constants.d.ts.map +1 -1
- package/dist/modules/notification/context.d.ts +4 -0
- package/dist/modules/notification/context.d.ts.map +1 -1
- package/dist/modules/notification/hooks.d.ts +1 -1
- package/dist/modules/notification/index.d.ts +2 -3
- package/dist/modules/notification/index.d.ts.map +1 -1
- package/dist/modules/notification/index.js +70 -154
- package/dist/modules/notification/index.js.map +1 -1
- package/dist/modules/notification/module.d.ts.map +1 -1
- package/dist/provider.js +255 -257
- package/dist/provider.js.map +1 -1
- package/dist/theme.d.ts +1 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +2 -2
- package/dist/theme.js.map +1 -1
- package/dist/utils/index.d.ts +5 -6
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -57
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/number.d.ts +0 -1
- package/dist/utils/number.d.ts.map +1 -1
- package/dist/utils/object.d.ts +0 -2
- package/dist/utils/object.d.ts.map +1 -1
- package/dist/utils/string.d.ts +0 -3
- package/dist/utils/string.d.ts.map +1 -1
- package/dist/utils/timing.d.ts +0 -1
- package/dist/utils/timing.d.ts.map +1 -1
- package/docs/README.md +124 -0
- package/docs/architecture-and-rules.md +129 -0
- package/docs/core-engine.md +196 -0
- package/docs/custom-modules.md +267 -0
- package/docs/getting-started.md +199 -0
- package/docs/modules/README.md +176 -0
- package/docs/modules/ambient.md +150 -0
- package/docs/modules/background.md +195 -0
- package/docs/modules/context-menu.md +248 -0
- package/docs/modules/controls.md +159 -0
- package/docs/modules/dock.md +734 -0
- package/docs/modules/error-boundary.md +160 -0
- package/docs/modules/kernel.md +266 -0
- package/docs/modules/loading.md +157 -0
- package/docs/modules/media.md +196 -0
- package/docs/modules/modal.md +229 -0
- package/docs/modules/notification.md +161 -0
- package/docs/modules/result.md +135 -0
- package/docs/testing.md +160 -0
- package/docs/theming.md +185 -0
- package/package.json +50 -2
- package/templates/AGENTS.md +165 -0
- package/dist/hooks/use-async-action.d.ts +0 -3
- package/dist/hooks/use-async-action.d.ts.map +0 -1
- package/dist/hooks/use-debounce.d.ts +0 -2
- package/dist/hooks/use-debounce.d.ts.map +0 -1
- package/dist/hooks/use-intersection-observer.d.ts +0 -4
- package/dist/hooks/use-intersection-observer.d.ts.map +0 -1
- package/dist/hooks/use-media-query.d.ts +0 -2
- package/dist/hooks/use-media-query.d.ts.map +0 -1
- package/dist/hooks/use-mounted.d.ts +0 -2
- package/dist/hooks/use-mounted.d.ts.map +0 -1
- package/dist/hooks/use-server-action.d.ts +0 -4
- package/dist/hooks/use-server-action.d.ts.map +0 -1
- package/dist/hooks/use-storage-state.d.ts +0 -5
- package/dist/hooks/use-storage-state.d.ts.map +0 -1
- package/dist/modules/theme.d.ts +0 -3
- package/dist/modules/theme.d.ts.map +0 -1
- package/dist/utils/json.d.ts +0 -3
- package/dist/utils/json.d.ts.map +0 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Getting Started with `@omerdlw/base-framework`
|
|
2
|
+
|
|
3
|
+
> **A complete step-by-step guide to installing, configuring, and building app-like Next.js 16 (App Router) & React 19 web applications with `@omerdlw/base-framework`.**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Prerequisites & Tech Stack
|
|
8
|
+
|
|
9
|
+
Before installing `@omerdlw/base-framework`, verify your environment:
|
|
10
|
+
- **Node.js:** `v20.0.0` or higher (Node 22 recommended)
|
|
11
|
+
- **Framework:** Next.js `15.0.0` or `16.0.0+` (App Router)
|
|
12
|
+
- **React:** React `19.0.0+` & React DOM `19.0.0+`
|
|
13
|
+
- **Styling:** Tailwind CSS v4
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 2. Installation
|
|
18
|
+
|
|
19
|
+
Install `@omerdlw/base-framework` alongside its required peer dependencies:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @omerdlw/base-framework motion @iconify-icon/react @radix-ui/react-tooltip clsx tailwind-merge
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Peer Dependencies Breakdown
|
|
26
|
+
|
|
27
|
+
| Package | Purpose in Base Framework |
|
|
28
|
+
| :--- | :--- |
|
|
29
|
+
| `motion` (`>=12.0.0`) | Hardware GPU-accelerated motion presets (`translate3d`, `scale`, spring physics). |
|
|
30
|
+
| `@iconify-icon/react` | Unified SVG-free icon rendering via `<Icon icon="solar:..." />`. |
|
|
31
|
+
| `@radix-ui/react-tooltip` | Accessible WAI-ARIA tooltip primitives. |
|
|
32
|
+
| `clsx` & `tailwind-merge` | Backing utilities for `cn()` class merging. |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 3. Tailwind CSS v4 Configuration
|
|
37
|
+
|
|
38
|
+
To ensure Tailwind scans the components, overlays, and atoms bundled inside `@omerdlw/base-framework`, add the package source to your main CSS file (e.g. `src/app/globals.css`):
|
|
39
|
+
|
|
40
|
+
```css
|
|
41
|
+
@import "tailwindcss";
|
|
42
|
+
|
|
43
|
+
/* 1. Instruct Tailwind to scan the Base Framework package files */
|
|
44
|
+
@source "../node_modules/@omerdlw/base-framework";
|
|
45
|
+
|
|
46
|
+
/* 2. Custom helper utility classes used by Base Framework */
|
|
47
|
+
@utility center {
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@utility skeleton-block {
|
|
54
|
+
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
55
|
+
background-color: rgb(255 255 255 / 0.08);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 4. Setting Up Application Providers
|
|
62
|
+
|
|
63
|
+
Create a client-side provider component (e.g., `src/app/providers.tsx`) to mount `CoreProvider` and install your desired UI modules:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
"use client";
|
|
67
|
+
|
|
68
|
+
import React from "react";
|
|
69
|
+
import { CoreProvider } from "@omerdlw/base-framework/provider";
|
|
70
|
+
import { ThemeProvider } from "@omerdlw/base-framework/theme";
|
|
71
|
+
|
|
72
|
+
// Import desired first-party modules
|
|
73
|
+
import { dockModule } from "@omerdlw/base-framework/modules/dock";
|
|
74
|
+
import { modalModule } from "@omerdlw/base-framework/modules/modal";
|
|
75
|
+
import { notificationModule } from "@omerdlw/base-framework/modules/notification";
|
|
76
|
+
import { ambientModule } from "@omerdlw/base-framework/modules/ambient";
|
|
77
|
+
import { backgroundModule } from "@omerdlw/base-framework/modules/background";
|
|
78
|
+
import { controlsModule } from "@omerdlw/base-framework/modules/controls";
|
|
79
|
+
import { loadingModule } from "@omerdlw/base-framework/modules/loading";
|
|
80
|
+
import { contextMenuModule } from "@omerdlw/base-framework/modules/context-menu";
|
|
81
|
+
|
|
82
|
+
// Define the modules your application uses
|
|
83
|
+
const modules = [
|
|
84
|
+
dockModule,
|
|
85
|
+
modalModule,
|
|
86
|
+
notificationModule,
|
|
87
|
+
ambientModule,
|
|
88
|
+
backgroundModule,
|
|
89
|
+
controlsModule,
|
|
90
|
+
loadingModule,
|
|
91
|
+
contextMenuModule,
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
95
|
+
return (
|
|
96
|
+
<CoreProvider modules={modules}>
|
|
97
|
+
{children}
|
|
98
|
+
</CoreProvider>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Then wrap your Root Layout in `src/app/layout.tsx`:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import "./globals.css";
|
|
107
|
+
import { Providers } from "./providers";
|
|
108
|
+
|
|
109
|
+
export const metadata = {
|
|
110
|
+
title: "My Base Framework App",
|
|
111
|
+
description: "Next.js application built with Base Framework",
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
115
|
+
return (
|
|
116
|
+
<html lang="en">
|
|
117
|
+
<body className="min-h-screen bg-black text-white antialiased">
|
|
118
|
+
<Providers>{children}</Providers>
|
|
119
|
+
</body>
|
|
120
|
+
</html>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 5. Building Your First Route with `usePage()`
|
|
128
|
+
|
|
129
|
+
Downstream routes never manually render headers, docks, or modals. Instead, a route simply declares its desired visual state using `usePage()`:
|
|
130
|
+
|
|
131
|
+
Create `src/app/page.tsx`:
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
"use client";
|
|
135
|
+
|
|
136
|
+
import { usePage } from "@omerdlw/base-framework/kernel";
|
|
137
|
+
import { Button, Icon } from "@omerdlw/base-framework/atoms";
|
|
138
|
+
|
|
139
|
+
export default function HomePage() {
|
|
140
|
+
const page = usePage({
|
|
141
|
+
title: "Dashboard",
|
|
142
|
+
dock: {
|
|
143
|
+
description: "Application Overview & Analytics",
|
|
144
|
+
icon: "solar:widget-bold",
|
|
145
|
+
},
|
|
146
|
+
background: {
|
|
147
|
+
image: "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe",
|
|
148
|
+
overlay: true,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<main className="p-8">
|
|
154
|
+
<h1 className="text-3xl font-bold tracking-tight">Dashboard Overview</h1>
|
|
155
|
+
<p className="mt-2 text-white/60">
|
|
156
|
+
Notice how the dock, background, and title were automatically configured by the microkernel.
|
|
157
|
+
</p>
|
|
158
|
+
|
|
159
|
+
<div className="mt-6 flex gap-3">
|
|
160
|
+
<Button
|
|
161
|
+
onClick={() => page.modules.notification?.toast("Action triggered successfully!", { type: "success" })}
|
|
162
|
+
className="inline-flex h-9 items-center gap-2 rounded-xl bg-white px-4 text-xs font-semibold text-black hover:bg-white/90"
|
|
163
|
+
>
|
|
164
|
+
<Icon icon="solar:bell-bold" size={16} />
|
|
165
|
+
Show Notification
|
|
166
|
+
</Button>
|
|
167
|
+
</div>
|
|
168
|
+
</main>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 6. Official Package Subpaths
|
|
176
|
+
|
|
177
|
+
`@omerdlw/base-framework` exposes 11 tree-shakable subpaths. Always import from the dedicated subpath:
|
|
178
|
+
|
|
179
|
+
| Subpath | Purpose | Key Exports |
|
|
180
|
+
| :--- | :--- | :--- |
|
|
181
|
+
| `@omerdlw/base-framework/kernel` | Microkernel engine | `usePage()`, `defineModule()`, `definePeer()`, `useModule()`, `useModuleState()` |
|
|
182
|
+
| `@omerdlw/base-framework/provider` | Root host component | `CoreProvider` |
|
|
183
|
+
| `@omerdlw/base-framework/modules/*` | 9 UI modules | `dockModule`, `modalModule`, `notificationModule`, `ambientModule`, etc. |
|
|
184
|
+
| `@omerdlw/base-framework/result` | Functional error handling | `Result<T, E>`, `ok()`, `err()`, `isResult()`, `createSafeAction()` |
|
|
185
|
+
| `@omerdlw/base-framework/events` | Cross-cutting event bus | `globalEvents`, `EVENT_TYPES`, `FrameworkEventMap` |
|
|
186
|
+
| `@omerdlw/base-framework/theme` | Declarative theme engine | `defineThemeSpec()`, `defineTheme()`, `ThemeProvider`, `useTheme()` |
|
|
187
|
+
| `@omerdlw/base-framework/atoms` | Unstyled accessible UI atoms | `Button`, `Icon`, `Spinner`, `Tooltip` |
|
|
188
|
+
| `@omerdlw/base-framework/hooks` | Universal React hooks | `useStore()`, `useClickOutside()`, `useGlobalEvent()`, `useRequiredContext()` |
|
|
189
|
+
| `@omerdlw/base-framework/utils` | Pure runtime utilities | `cn()`, `clamp()`, `debounce()`, `throttle()`, `createStore()`, `createScheduler()`, `report()` |
|
|
190
|
+
| `@omerdlw/base-framework/error` | Isolated error boundaries | `GlobalError`, `ModuleError`, `UserError`, `toUserMessage()` |
|
|
191
|
+
| `@omerdlw/base-framework/tokens` | Design & motion tokens | `DURATION_TOKENS`, `EASING_CURVES`, `SPRING_PRESETS`, `Z_INDEX` |
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 7. Next Steps
|
|
196
|
+
|
|
197
|
+
- Explore how visual design is configured via [Theming Guide (`theming.md`)](./theming.md).
|
|
198
|
+
- Learn how to build custom modules via [Custom Module Authoring (`custom-modules.md`)](./custom-modules.md).
|
|
199
|
+
- Read the [Architecture & 10 Rules (`architecture-and-rules.md`)](./architecture-and-rules.md) for strict architectural invariants.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Module Reference
|
|
2
|
+
|
|
3
|
+
> **Read this first if you need to:** find which module does what, see how modules connect, add a new module, or learn the structure every module page follows.
|
|
4
|
+
|
|
5
|
+
One page per module/core entry. Each page answers, from the big picture down to the details: **what it is → mental model → quick start → capabilities → full API → behaviour → theming → recipes/gotchas → file map → dependencies**.
|
|
6
|
+
|
|
7
|
+
## 1. Index
|
|
8
|
+
|
|
9
|
+
| Module | Import | Doc | What it owns | Page key (`usePage`) | Registry type |
|
|
10
|
+
| :------------- | :------------------------------------------ | :--------------------------------------- | :------------------------------------------------------------------------------------------------- | :------------------- | :------------ |
|
|
11
|
+
| Kernel | `@omerdlw/base-framework/kernel` | [kernel.md](./kernel.md) | Module host, registry, `usePage`, `defineModule`, peers | `title`, `registry` | — |
|
|
12
|
+
| Dock | `@omerdlw/base-framework/modules/dock` | [dock.md](./dock.md) | The only chrome: card stack, surfaces/flows, HUD, operations, status overlays, guards, breadcrumbs | `dock` | `dock` |
|
|
13
|
+
| Modal | `@omerdlw/base-framework/modules/modal` | [modal.md](./modal.md) | Promise-based stacked dialogs | `modal` | `modal` |
|
|
14
|
+
| Notification | `@omerdlw/base-framework/modules/notification` | [notification.md](./notification.md) | Single-slot toasts docked to the dock; event-driven feedback | `notification` | — |
|
|
15
|
+
| Loading | `@omerdlw/base-framework/modules/loading` | [loading.md](./loading.md) | Loading overlay + busy flag | `loading` | `loading` |
|
|
16
|
+
| Background | `@omerdlw/base-framework/modules/background` | [background.md](./background.md) | Image / video / YouTube layer behind the page | `background` | `background` |
|
|
17
|
+
| Media | `@omerdlw/base-framework/modules/media` | [media.md](./media.md) | The active media session the dock controls operate on | `media` | `media` |
|
|
18
|
+
| Ambient | `@omerdlw/base-framework/modules/ambient` | [ambient.md](./ambient.md) | Image → palette → CSS color variables | `ambient` | — |
|
|
19
|
+
| Controls | `@omerdlw/base-framework/modules/controls` | [controls.md](./controls.md) | Paired control rails beside the dock | `controls` | `controls` |
|
|
20
|
+
| Context menu | `@omerdlw/base-framework/modules/context-menu` (id `contextMenu`) | [context-menu.md](./context-menu.md) | Declarative right-click menus | `contextMenu` | `contextMenu` |
|
|
21
|
+
| Error boundary | `@omerdlw/base-framework/error` | [error-boundary.md](./error-boundary.md) | Boundaries, reporter, window error listener | — | — |
|
|
22
|
+
| Result | `@omerdlw/base-framework/result` | [result.md](./result.md) | `Result<T, E>`, `createSafeAction` | — | — |
|
|
23
|
+
|
|
24
|
+
General guides: [../getting-started.md](../getting-started.md) (installation & setup), [../architecture-and-rules.md](../architecture-and-rules.md) (rules and boundaries), [../theming.md](../theming.md) (custom themes), [../custom-modules.md](../custom-modules.md) (custom module authoring), [../core-engine.md](../core-engine.md) (provider, theme, events, hooks, utils, tokens), [../testing.md](../testing.md) (testing guide).
|
|
25
|
+
|
|
26
|
+
## 2. How the modules connect
|
|
27
|
+
|
|
28
|
+
Modules never import each other. They connect through three channels only:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
┌────────────┐ usePage({ dock, background, loading, … }) ┌──────────┐
|
|
32
|
+
│ pages/ │ ───────────────────────────────────────────▶ │ registry │ (per module type)
|
|
33
|
+
│ features │ └────┬─────┘
|
|
34
|
+
└─────┬──────┘ │ useRegistryEntries / useRegistryValue
|
|
35
|
+
│ globalEvents (DOCK_STATUS_SET, AUTH_*, APP_ERROR …) ▼
|
|
36
|
+
▼ each module's provider/overlay
|
|
37
|
+
┌────────────┐ definePeer / useModuleState (declared in `uses`)
|
|
38
|
+
│ modules │ ◀────────────────────────────────────────────▶ other modules' stores
|
|
39
|
+
└────────────┘ DOM contracts: #dock-card-stack, data-controls-hidden, data-controls-anchor
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
| Reader → source | How | Used for |
|
|
43
|
+
| :---------------------------- | :----------------------- | :--------------------------------------------------------------------------- |
|
|
44
|
+
| dock → `loading` | `definePeer` | Page-loading flag; `stopLoading()` after navigation. |
|
|
45
|
+
| dock → `media` | `definePeer` | Play/pause icon, media controls. |
|
|
46
|
+
| dock → `notification` | `definePeer` | Is a toast visible (breadcrumbs yield). |
|
|
47
|
+
| background → `media` | `definePeer` | Registers the background video as source `background`. |
|
|
48
|
+
| ambient → `background` | `useModuleState` | Poster URL as the default tint image. |
|
|
49
|
+
| context-menu → `dock` | `useModuleState` | Active card as the default menu header. |
|
|
50
|
+
| notification, controls → dock | DOM id `dock-card-stack` | Portal target / rail anchor. |
|
|
51
|
+
| dock ← events | `globalEvents` | `APP_ERROR`, `API_ERROR`, `DOCK_STATUS_SET`, `DOCK_GUARD`, `DOCK_NOT_FOUND`. |
|
|
52
|
+
| notification ← events | `globalEvents` | `API_UNAUTHORIZED`, `APP_ERROR`/`STATE_CHANGE` with `notify: true`. |
|
|
53
|
+
|
|
54
|
+
Every `definePeer`/`useModuleState`/`useModule` call needs the **peer id as a string literal** and the id in the module's `uses` (`npm run check:architecture` verifies it). Each peer ships an inert stand-in, so any module can be left out of `CoreProvider modules` and the rest still works.
|
|
55
|
+
|
|
56
|
+
## 3. Install-time facts
|
|
57
|
+
|
|
58
|
+
- `src/modules/index.ts` exports every module and `defaultModules` (`background, ambient, dock, controls, contextMenu, loading, media, modal, notification`); it is the only place that lists them all. `src/app/providers.tsx` passes the list to `CoreProvider`.
|
|
59
|
+
- Install a subset to ship less: `modules={[dockModule, notificationModule, loadingModule]}`.
|
|
60
|
+
- Module ids are **camelCase** (`contextMenu`); the folder and theme id may be kebab-case (`context-menu`).
|
|
61
|
+
- A module is installable when it has a `module.tsx` (or `module.ts`) with `defineModule`; the order of the list does not matter (`uses` sorts it).
|
|
62
|
+
|
|
63
|
+
## 4. Canonical module template
|
|
64
|
+
|
|
65
|
+
Every folder in `src/modules/` uses the same **slots**. A slot that does not apply is omitted, never created empty. `tests/architecture.test.ts` fails when a required file or the module's doc page is missing, or when an unexpected file appears at the module root.
|
|
66
|
+
|
|
67
|
+
| File | Required | Responsibility |
|
|
68
|
+
| :------------- | :-----------------: | :------------------------------------------------------------------------------------------------------------------------------ |
|
|
69
|
+
| `index.ts` | ✅ | Public barrel: named exports only; nothing internal leaks through it. |
|
|
70
|
+
| `types.ts` | ✅ | Public TypeScript contracts, **no runtime code** (a `types/` folder with an `index.ts` is also accepted). |
|
|
71
|
+
| `constants.ts` | ✅ | Frozen configuration, enums, event names, the theme spec (`defineThemeSpec`). |
|
|
72
|
+
| `utils.ts` | ✅ | Pure helpers (no React state). |
|
|
73
|
+
| `module.tsx` | installable modules | `defineModule` definition, `defineX` builder, `declare module "@omerdlw/base-framework/kernel"` augmentation (`module.ts` when there is no JSX). |
|
|
74
|
+
| `context.tsx` | when stateful | The context, the provider and the `useX` hooks (**one** context per module). |
|
|
75
|
+
| `hooks.ts` | optional | Extra hooks, notably the `use<Component>Model` logic behind each overlay component. |
|
|
76
|
+
| `state.ts` | optional | State factory, initial state, pure reducers. |
|
|
77
|
+
| `overlay.tsx` | optional | **All** visual components: JSX only, calling `use*Model` hooks (no state, effects or refs). |
|
|
78
|
+
| `motion.ts` | optional | Motion presets derived from `@omerdlw/base-framework/tokens`. |
|
|
79
|
+
| `store.ts` | optional | A store file when warranted. |
|
|
80
|
+
|
|
81
|
+
Rules of thumb:
|
|
82
|
+
|
|
83
|
+
- A module may add a **topic file or folder** for a cohesive concern, declared in the file-pattern test (`background/youtube/`, `context-menu/resolver.ts`, the dock's `routing/ runtime/ status/ surface/ cards.tsx hud.ts provider.tsx`). Files past ~500 lines go into a sub-folder.
|
|
84
|
+
- Relative imports may reach into the **same** module only. Cross-module needs go through peers or `globalEvents`.
|
|
85
|
+
- Barrels use explicit named exports (`export { a, b } from "./x"`), never `export *` from implementation files; `export type * from "./types"` is fine.
|
|
86
|
+
- **No design in code:** colors, sizes, radii and typography are theme slots (`src/config/<name>.module.theme.ts`); animation comes from `@omerdlw/base-framework/tokens` presets.
|
|
87
|
+
|
|
88
|
+
### 4.1 File matrix (current)
|
|
89
|
+
|
|
90
|
+
| Module | `index types constants utils` | `module` | `context` | `hooks` | `state` | `overlay` | `motion` | Extras |
|
|
91
|
+
| :----------- | :---------------------------: | :---------: | :----------: | :-----: | :-----: | :-------: | :------: | :----------------------------------------------------------------------------------- |
|
|
92
|
+
| ambient | ✅ | ✅ | ✅ | — | — | — | — | |
|
|
93
|
+
| background | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ | `youtube/` |
|
|
94
|
+
| context-menu | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | `resolver.ts` |
|
|
95
|
+
| controls | ✅ | ✅ | — | ✅ | — | ✅ | — | |
|
|
96
|
+
| dock | ✅ | ✅ | `context.ts` | ✅ | ✅ | ✅ | ✅ | `provider.tsx`, `cards.tsx`, `hud.ts`, `routing/`, `runtime/`, `status/`, `surface/` |
|
|
97
|
+
| loading | ✅ | ✅ | ✅ | ✅ | — | ✅ | — | |
|
|
98
|
+
| media | ✅ | ✅ | ✅ | ✅ | — | ✅ | — | |
|
|
99
|
+
| modal | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
|
|
100
|
+
| notification | ✅ | `module.ts` | ✅ | ✅ | — | ✅ | ✅ | |
|
|
101
|
+
|
|
102
|
+
### 4.2 Why some optional slots are absent
|
|
103
|
+
|
|
104
|
+
| Module | Absent | Reason |
|
|
105
|
+
| :------------- | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------- |
|
|
106
|
+
| `ambient` | `overlay`, `motion` | Renders nothing: it writes CSS custom properties; transitions are CSS classes from the theme. |
|
|
107
|
+
| `controls` | `context`, `motion` | Stateless: entries live in the registry and layout is measured from the DOM. No animation. |
|
|
108
|
+
| `loading` | `motion`, `state` | The overlay renders `Spinner` (or a skeleton) and mounts/unmounts instantly; state is a small `useState` in the provider. |
|
|
109
|
+
| `media` | `motion` | Renders nothing; `overlay.tsx` holds only the components that return `null` (`MediaSource`, `MediaOverlay`). |
|
|
110
|
+
| `notification` | builder | Toasts are imperative: `useToast` (in `hooks.ts`) plays the builder role. |
|
|
111
|
+
| core `error` | provider, builder, view | Boundaries are class components configured through props; `boundary.tsx` is the view, `listener.ts` and `reporter.ts` are topic files. |
|
|
112
|
+
|
|
113
|
+
## 5. Adding a module
|
|
114
|
+
|
|
115
|
+
1. **Create** `src/modules/<name>/` with `index.ts`, `types.ts`, `constants.ts`, `utils.ts`, and (as needed) `context.tsx`, `hooks.ts`, `overlay.tsx`, `motion.ts`, `module.tsx`.
|
|
116
|
+
2. **Define it** in `module.tsx`:
|
|
117
|
+
```ts
|
|
118
|
+
export const announcementModule = defineModule({
|
|
119
|
+
id: "announcement", // camelCase
|
|
120
|
+
context: AnnouncementContext,
|
|
121
|
+
Provider: AnnouncementProvider,
|
|
122
|
+
Overlay: AnnouncementBanner,
|
|
123
|
+
uses: ["dock"], // only peers you read via definePeer/useModuleState
|
|
124
|
+
registry: {
|
|
125
|
+
keyPolicy: "singleton",
|
|
126
|
+
singletonKey: "current",
|
|
127
|
+
lifecycle: "route",
|
|
128
|
+
},
|
|
129
|
+
page: {
|
|
130
|
+
entries: (slice) => [{ key: "current", value: slice }],
|
|
131
|
+
use: useAnnouncementPage,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
declare module "@omerdlw/base-framework/kernel" {
|
|
135
|
+
interface CoreModules {
|
|
136
|
+
announcement: typeof announcementModule;
|
|
137
|
+
}
|
|
138
|
+
interface PageConfig {
|
|
139
|
+
announcement?: AnnouncementConfig;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
3. **Theme it:** `defineThemeSpec<Slots>("announcement")` in `constants.ts`, `src/config/announcement.module.theme.ts` with `defineTheme`, and list it in `src/config/index.ts` (otherwise `useTheme` throws).
|
|
144
|
+
4. **Export it** from `src/modules/index.ts` (and add it to `defaultModules` if it ships by default).
|
|
145
|
+
5. **Document it** at `src/docs/modules/announcement.md` using the page structure in §6 and add a row to the index above.
|
|
146
|
+
6. **Test it** in `tests/modules/announcement.test.ts` ([testing.md](../testing.md#41-adding-tests-for-a-new-module)); register its registry type in `tests/support/registry.ts` if it has one.
|
|
147
|
+
7. **Run the pre-flight** (`type-check`, `lint`, `check:architecture`, `test`). The architecture test verifies the template, the doc page, overlay purity and one-context-per-module.
|
|
148
|
+
|
|
149
|
+
In downstream projects `src/modules` is yours: add, change or delete modules there without touching `src/core`.
|
|
150
|
+
|
|
151
|
+
## 6. Structure of a module page
|
|
152
|
+
|
|
153
|
+
Every page follows this skeleton (omit sections that do not apply). Keep it **verified against the code**: names, defaults, numbers and file maps must match the source.
|
|
154
|
+
|
|
155
|
+
1. **Title + "Read this first if you need to…"** one-liner.
|
|
156
|
+
2. **At a glance** table: import, id, what it renders, registry type, page key, peers, theme, events/DOM contracts, tests.
|
|
157
|
+
3. **Mental model** (diagram or short rules).
|
|
158
|
+
4. **Quick start** with the 80 % usage.
|
|
159
|
+
5. **Capabilities** (matrix with where to read more).
|
|
160
|
+
6. **API reference**: every public export with signature, defaults and behaviour; option tables.
|
|
161
|
+
7. **Behaviour details**: lifecycle, ordering, edge cases, accessibility.
|
|
162
|
+
8. **Theming**: slots and what stays in code.
|
|
163
|
+
9. **Recipes and gotchas**.
|
|
164
|
+
10. **File map** (every file, one responsibility each).
|
|
165
|
+
11. **Dependencies** (uses / peers / used by).
|
|
166
|
+
|
|
167
|
+
## 7. Dependency direction
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
src/app ─┐
|
|
171
|
+
src/features ─┼─▶ @omerdlw/base-framework/modules/* ──▶ @omerdlw/base-framework/kernel ──▶ @omerdlw/base-framework/{utils,hooks,tokens,events,result}
|
|
172
|
+
src/core/provider.tsx ┘ │
|
|
173
|
+
└──▶ @omerdlw/base-framework/atoms, @omerdlw/base-framework/theme
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Modules are installed by `CoreProvider` from the list the app passes; orchestration drives modules through their `defineModule` definitions and never imports them.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Ambient — `@omerdlw/base-framework/modules/ambient`
|
|
2
|
+
|
|
3
|
+
> **Read this first if you need to:** tint a page from an image (cover, avatar, album art), set theme colors from code, scope a tint to one element, or know which CSS variables change.
|
|
4
|
+
|
|
5
|
+
## 1. At a glance
|
|
6
|
+
|
|
7
|
+
| | |
|
|
8
|
+
| :----------------- | :----------------------------------------------------------------------------- |
|
|
9
|
+
| **Import** | `import { useAmbientTheme, defineAmbient, … } from "@omerdlw/base-framework/modules/ambient"` |
|
|
10
|
+
| **Module id** | `ambient` (`ambientModule`, in `defaultModules`) |
|
|
11
|
+
| **Renders** | Nothing. It writes **CSS custom properties** on an element (default `<html>`). |
|
|
12
|
+
| **Registry type** | none (no `registry` definition; it only uses the page hook) |
|
|
13
|
+
| **Page slice** | `usePage({ ambient })` and the dock banner → `page.modules.ambient.set(...)` |
|
|
14
|
+
| **Peers (`uses`)** | `background` (reads `posterUrl` through `useModuleState`) |
|
|
15
|
+
| **Theme** | `ambientTheme` → one slot, `transition` (`src/config/ambient.module.theme.ts`) |
|
|
16
|
+
| **HTTP route** | `GET /api/ambient/proxy?url=…` (CORS-safe image proxy, used as a fallback) |
|
|
17
|
+
| **Tests** | `tests/modules/ambient.test.ts` |
|
|
18
|
+
|
|
19
|
+
## 2. Mental model
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
image URL ──fetch (CORS) ─┬─▶ 32×32 canvas ─▶ OKLCH sampling ─▶ { primary, black } palette
|
|
23
|
+
└─ fallback: /api/ambient/proxy
|
|
24
|
+
palette ─▶ CSS variables on the target element ─▶ every rule that reads them re-tints
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Ambient is a **colour pipeline**, not a component: pick an image (or pass colors directly), and the variables the design system already reads (`--primary`, `--black`, …) change. Because they are plain CSS variables, Tailwind classes such as `bg-primary` / `text-primary` and anything using `var(--black)` follow automatically, with a CSS color transition.
|
|
28
|
+
|
|
29
|
+
## 3. Quick start
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
"use client";
|
|
33
|
+
// 1. Through usePage: a string is the image
|
|
34
|
+
usePage({ ambient: "/images/album-cover.jpg" });
|
|
35
|
+
|
|
36
|
+
// 2. `true` → use the dock banner, else the background video's poster; `false` → turn the automatic banner tint off
|
|
37
|
+
usePage({ dock: { banner: coverUrl }, ambient: true });
|
|
38
|
+
|
|
39
|
+
// 3. From any component
|
|
40
|
+
useAmbientTheme({ image: backdropUrl || account?.avatarUrl || null });
|
|
41
|
+
|
|
42
|
+
// 4. Explicit colors, scoped to one element
|
|
43
|
+
const cardRef = useRef<HTMLDivElement>(null);
|
|
44
|
+
useAmbientTheme({
|
|
45
|
+
colors: { primary: "#e5d3ff", black: "#140d1f" },
|
|
46
|
+
scope: cardRef,
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 4. What gets written
|
|
51
|
+
|
|
52
|
+
| Variable | Set when | Value |
|
|
53
|
+
| :----------------------------- | :------------------------------------------- | :----------------------------------------------------------- |
|
|
54
|
+
| `--primary` | an `image` is given and extracted | bright OKLCH colour (lightness 0.74, chroma 0.15–0.24) |
|
|
55
|
+
| `--color-ambient-glow` | same | same as `--primary` |
|
|
56
|
+
| `--color-primary` | same **and** `tintGlobals` is true (default) | same as `--primary` |
|
|
57
|
+
| `--black` | an `image` is given and extracted | deep, slightly tinted OKLCH (lightness 0.15, chroma ≤ 0.055) |
|
|
58
|
+
| any variable named in `colors` | `colors` is set | the literal value |
|
|
59
|
+
|
|
60
|
+
`colors` keys are mapped as: `primary` → `--primary`, `black` → `--black`, `white` → `--white`, `ambientGlow` → `--color-ambient-glow`, `colorPrimary` → `--color-primary`; a key starting with `--` is used verbatim; anything else becomes `--color-<key>`. `colors` always wins over extracted values.
|
|
61
|
+
|
|
62
|
+
On cleanup the previous inline values (and priorities) are restored exactly. If `transition` is true (default) the theme's `transition` classes (`transition-colors duration-moderate ease-out-quart`) are added to the target while the theme is active (and removed again only for scoped targets, so the global tint keeps animating between pages).
|
|
63
|
+
|
|
64
|
+
## 5. Public API
|
|
65
|
+
|
|
66
|
+
| Export | Kind | Description |
|
|
67
|
+
| :----------------------------------------------------------------------------------------------- | :-------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
68
|
+
| `ambientModule` | module | `defineModule` definition (provider + `usePage({ ambient })`). |
|
|
69
|
+
| `AmbientProvider` | component | Holds the **current shared palette** and `isExtracting` in a store. Prop `initialPalette`. |
|
|
70
|
+
| `useAmbientTheme(config)` | hook | **Main entry.** `config` is an image URL string or `AmbientConfig`. Extracts, applies CSS variables, updates the shared palette. Returns `{ palette, isExtracting }`. |
|
|
71
|
+
| `useAmbientColor(image, options?)` | hook | Extract a palette **without** applying it. |
|
|
72
|
+
| `useAmbient(configOrImage?)` | hook | The shared state `{ palette, isExtracting, setPalette, setIsExtracting }`; with an argument it also applies that theme and returns its palette. Throws outside `AmbientProvider`. |
|
|
73
|
+
| `defineAmbient(def)` | builder | Reusable theme; `image` / `colors` may be functions of props. Returns `{ id, config, create(props?, overrides?), use(props?, overrides?) }`. |
|
|
74
|
+
| `extractPaletteFromImage(src, options?)` | util | Async extraction with cache and in-flight de-duplication; resolves the fallback palette on any failure. |
|
|
75
|
+
| `rgbToOklch(r, g, b)`, `oklchToString(l, c, h, alpha?)` | utils | Colour conversion helpers. |
|
|
76
|
+
| `resolvePageAmbientTheme(slice, posterUrl)` | util | Maps the page's `ambient` + dock banner to a theme config. |
|
|
77
|
+
| `AMBIENT_CSS_VARS`, `AMBIENT_DEFAULTS`, `COLOR_EXTRACT_CONFIG`, `ambientTheme`, `AmbientContext` | constants | |
|
|
78
|
+
| Types | | `AmbientConfig`, `AmbientPalette`, `AmbientDescriptor`, `AmbientTarget`, `AmbientImageSource`, `AmbientPageConfig`, … |
|
|
79
|
+
|
|
80
|
+
### 5.1 `AmbientConfig`
|
|
81
|
+
|
|
82
|
+
| Field | Default | Meaning |
|
|
83
|
+
| :--------------- | :---------------------------------- | :---------------------------------------------------------------------------------------------- |
|
|
84
|
+
| `image` | `null` | URL string or `{ src }`. |
|
|
85
|
+
| `colors` | `null` | Explicit variables (see §4). A config with only `colors` is active without extraction. |
|
|
86
|
+
| `scope` | `null` → `document.documentElement` | CSS selector string, element, or `ref`. A selector that matches nothing applies nothing. |
|
|
87
|
+
| `tintGlobals` | `true` | Also set `--color-primary`. Set `false` to tint only `--primary`/`--black`/glow inside a scope. |
|
|
88
|
+
| `transition` | `true` | Add the theme's transition classes to the target. |
|
|
89
|
+
| `initialPalette` | defaults | Palette shown before extraction finishes. |
|
|
90
|
+
| `options` | `null` | `{ initialPalette, fallbackPalette }` (the fallback is used when extraction fails). |
|
|
91
|
+
|
|
92
|
+
`AMBIENT_DEFAULTS` = `{ primary: "#101010", black: "#0b0b0b" }` is used until a palette exists.
|
|
93
|
+
|
|
94
|
+
### 5.2 `usePage({ ambient })` resolution
|
|
95
|
+
|
|
96
|
+
`resolvePageAmbientTheme` runs with the page's `ambient`, the dock **banner**, and the background's `posterUrl`:
|
|
97
|
+
|
|
98
|
+
| `ambient` value | Result |
|
|
99
|
+
| :-------------- | :--------------------------------------------------------------------------------------------------- |
|
|
100
|
+
| `false` | No theme (also disables the banner tint). |
|
|
101
|
+
| `true` | `image` = banner, else the background video poster. |
|
|
102
|
+
| string | That image (`tintGlobals: true`). |
|
|
103
|
+
| object | `{ image: ambient.image ?? banner ?? poster, ...ambient }`. |
|
|
104
|
+
| not set | If the dock has a banner and the background has no poster → tint with the banner; otherwise nothing. |
|
|
105
|
+
|
|
106
|
+
`page.modules.ambient` is just `{ set(ambient) }`.
|
|
107
|
+
|
|
108
|
+
## 6. Extraction details
|
|
109
|
+
|
|
110
|
+
- **Source loading.** Remote (`http…`) images are first fetched with `cors=1` and `mode: "cors"`, then through `/api/ambient/proxy?url=…`; the blob is read from an object URL (revoked afterwards). Same-origin or non-http sources are loaded directly (with `crossOrigin="anonymous"` when no object URL was needed).
|
|
111
|
+
- **The proxy** only serves `image/*` (415 otherwise), blocks unsafe/internal URLs with `isSafeUrl` (including on every redirect, up to 5), and caches responses for a day. Do not point it at anything but images.
|
|
112
|
+
- **Sampling.** The image is drawn to a 32×32 canvas. Pixels with alpha < 128 are skipped; near-black, near-white and low-chroma pixels are counted as neutral. Remaining pixels go into 24 hue bins; the densest bin plus half-weighted neighbours gives the hue, chroma and lightness. An all-neutral image yields a muted blue (`h 252`).
|
|
113
|
+
- **Derivation.** Chroma is clamped to `0.03–0.28`; `primary` uses lightness `0.74` with chroma `max(0.15, min(0.24, max(chroma × 1.15, 0.18)))`; `black` uses lightness `0.15` with chroma `min(0.055, max(0.025, chroma × 0.35))`.
|
|
114
|
+
- **Cache.** Up to 100 palettes (`cacheLimit`, oldest evicted) and concurrent requests for the same URL share one promise.
|
|
115
|
+
- **Stability.** `colors`, `options` and `initialPalette` are stabilized with `shallowEqual`, so passing fresh object literals each render does **not** re-apply the theme. (A test forbids `JSON.stringify` for this.)
|
|
116
|
+
- **Failure.** Any failure resolves the fallback palette (`initialPalette` → `fallbackPalette` → defaults). It never throws.
|
|
117
|
+
|
|
118
|
+
## 7. Recipes and gotchas
|
|
119
|
+
|
|
120
|
+
| I want to… | Do this |
|
|
121
|
+
| :------------------------------ | :--------------------------------------------------------------------- |
|
|
122
|
+
| Tint a page from its hero image | `usePage({ ambient: heroUrl })` |
|
|
123
|
+
| Tint only one section | `useAmbientTheme({ image, scope: "#section", tintGlobals: false })` |
|
|
124
|
+
| Read the palette in JS | `const { palette } = useAmbientTheme(image)` or `useAmbient().palette` |
|
|
125
|
+
| Disable tint on one page | `usePage({ ambient: false })` |
|
|
126
|
+
| Brand colours without images | `useAmbientTheme({ colors: { primary: "#…" } })` |
|
|
127
|
+
|
|
128
|
+
- The theme is global by default: two components calling `useAmbientTheme` without `scope` fight over `--primary`; the last effect wins.
|
|
129
|
+
- Cross-origin images that block CORS **and** the proxy cannot be sampled; the fallback palette is used.
|
|
130
|
+
- `useAmbient()` without arguments only _reads_ the shared palette; it does not apply anything.
|
|
131
|
+
- Components should read the variables (`bg-primary`, `var(--black)`) rather than the palette object, so they follow transitions for free.
|
|
132
|
+
|
|
133
|
+
## 8. File map
|
|
134
|
+
|
|
135
|
+
| File | Responsibility |
|
|
136
|
+
| :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
137
|
+
| `index.ts` | Public barrel. |
|
|
138
|
+
| `types.ts` | Palette, config, descriptor, target/source, page contracts. |
|
|
139
|
+
| `constants.ts` | CSS variable names, defaults, extraction tuning, `ambientTheme`. |
|
|
140
|
+
| `utils.ts` | OKLCH conversion, canvas sampling, palette derivation, fetch/cache/extraction, target resolution, scoped CSS-variable application, `resolveAmbientVarMap`, `resolvePageAmbientTheme`. |
|
|
141
|
+
| `context.tsx` | `AmbientProvider` (shared palette store), `useAmbientColor`, `useAmbientTheme`, `useAmbient`. |
|
|
142
|
+
| `module.tsx` | `ambientModule`, `useAmbientPage` (applies the page theme), `defineAmbient`, kernel type augmentation. |
|
|
143
|
+
|
|
144
|
+
No `view.tsx` or `motion.ts`: the module renders nothing, and transitions are CSS classes from the theme ([README](./README.md#42-why-some-optional-slots-are-absent)).
|
|
145
|
+
|
|
146
|
+
## 9. Dependencies
|
|
147
|
+
|
|
148
|
+
- **Uses:** `@omerdlw/base-framework/kernel` (`defineModule`, `useModuleState`), `@omerdlw/base-framework/hooks` (`useRequiredContext`, `useStore`), `@omerdlw/base-framework/utils` (`createStore`, `shallowEqual`), `@omerdlw/base-framework/theme`; `src/app/api/ambient/proxy` and `@/infrastructure/security/url-safety` (server side).
|
|
149
|
+
- **Peer:** `background` (poster fallback), read through `useModuleState("background", …, null)`, so it works when background is not installed.
|
|
150
|
+
- **Used by:** `src/core/provider.tsx`, `usePage({ ambient })`, and features such as the account layout (`useAmbientTheme`).
|