@almadar/ui 5.76.0 → 5.76.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/dist/avl/index.cjs +1449 -1428
- package/dist/avl/index.js +250 -229
- package/dist/components/index.cjs +103 -46
- package/dist/components/index.js +103 -37
- package/dist/context/index.cjs +690 -47
- package/dist/context/index.js +682 -2
- package/dist/providers/index.cjs +2175 -1255
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +1087 -208
- package/dist/runtime/index.cjs +1310 -1289
- package/dist/runtime/index.js +230 -209
- package/package.json +1 -1
package/dist/context/index.js
CHANGED
|
@@ -1,4 +1,675 @@
|
|
|
1
|
-
|
|
1
|
+
import { createContext, useMemo, useContext, useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
import { createLogger } from '@almadar/logger';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
export { ANONYMOUS_USER, CurrentPagePathContext, CurrentPagePathProvider, OrbitalThemeProvider, UserContext, UserProvider, useCurrentPagePath, useHasPermission, useHasRole, useUser, useUserForEvaluation } from '@almadar/ui/providers';
|
|
5
|
+
import { ThemeProvider as ThemeProvider$1, useTheme as useTheme$1 } from '@almadar/ui/context';
|
|
6
|
+
|
|
7
|
+
var log = createLogger("almadar:ui:ui-slots");
|
|
8
|
+
var DEFAULT_SOURCE_KEY = "__default__";
|
|
9
|
+
var MULTI_SOURCE_STACK_TRAIT = "__multi_source_stack__";
|
|
10
|
+
var ALL_SLOTS = [
|
|
11
|
+
"main",
|
|
12
|
+
"sidebar",
|
|
13
|
+
"modal",
|
|
14
|
+
"drawer",
|
|
15
|
+
"overlay",
|
|
16
|
+
"center",
|
|
17
|
+
"toast",
|
|
18
|
+
"hud-top",
|
|
19
|
+
"hud-bottom",
|
|
20
|
+
"floating"
|
|
21
|
+
];
|
|
22
|
+
var DEFAULT_SLOTS = ALL_SLOTS.reduce(
|
|
23
|
+
(acc, slot) => {
|
|
24
|
+
acc[slot] = null;
|
|
25
|
+
return acc;
|
|
26
|
+
},
|
|
27
|
+
{}
|
|
28
|
+
);
|
|
29
|
+
var DEFAULT_SOURCES = ALL_SLOTS.reduce(
|
|
30
|
+
(acc, slot) => {
|
|
31
|
+
acc[slot] = {};
|
|
32
|
+
return acc;
|
|
33
|
+
},
|
|
34
|
+
{}
|
|
35
|
+
);
|
|
36
|
+
function aggregateSlot(sources) {
|
|
37
|
+
if (!sources) return null;
|
|
38
|
+
const entries = Object.entries(sources);
|
|
39
|
+
if (entries.length === 0) return null;
|
|
40
|
+
if (entries.length === 1) return entries[0][1];
|
|
41
|
+
const children = entries.map(([, entry]) => ({
|
|
42
|
+
type: entry.pattern,
|
|
43
|
+
...entry.props
|
|
44
|
+
}));
|
|
45
|
+
const stackId = `slot-content-stack-${entries.map(([k]) => k).join("-")}`;
|
|
46
|
+
return {
|
|
47
|
+
id: stackId,
|
|
48
|
+
pattern: "stack",
|
|
49
|
+
props: {
|
|
50
|
+
direction: "vertical",
|
|
51
|
+
gap: "lg",
|
|
52
|
+
children
|
|
53
|
+
},
|
|
54
|
+
priority: 0,
|
|
55
|
+
animation: "fade",
|
|
56
|
+
sourceTrait: MULTI_SOURCE_STACK_TRAIT
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function useUISlotManager() {
|
|
60
|
+
const [sources, setSources] = useState(DEFAULT_SOURCES);
|
|
61
|
+
const subscribersRef = useRef(/* @__PURE__ */ new Set());
|
|
62
|
+
const timersRef = useRef(/* @__PURE__ */ new Map());
|
|
63
|
+
const traitIndexRef = useRef(/* @__PURE__ */ new Map());
|
|
64
|
+
const traitSubscribersRef = useRef(/* @__PURE__ */ new Map());
|
|
65
|
+
const slots = useMemo(() => {
|
|
66
|
+
const out = { ...DEFAULT_SLOTS };
|
|
67
|
+
for (const slot of ALL_SLOTS) {
|
|
68
|
+
out[slot] = aggregateSlot(sources[slot]);
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}, [sources]);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
return () => {
|
|
74
|
+
timersRef.current.forEach((timer) => clearTimeout(timer));
|
|
75
|
+
timersRef.current.clear();
|
|
76
|
+
};
|
|
77
|
+
}, []);
|
|
78
|
+
const notifySubscribers = useCallback((slot, content) => {
|
|
79
|
+
subscribersRef.current.forEach((callback) => {
|
|
80
|
+
try {
|
|
81
|
+
callback(slot, content);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
log.error("Subscriber error", { error: error instanceof Error ? error : String(error) });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}, []);
|
|
87
|
+
const notifyTraitSubscribers = useCallback(
|
|
88
|
+
(traitName, content) => {
|
|
89
|
+
const subs = traitSubscribersRef.current.get(traitName);
|
|
90
|
+
if (!subs) return;
|
|
91
|
+
subs.forEach((callback) => {
|
|
92
|
+
try {
|
|
93
|
+
callback(content);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
log.error("Trait subscriber error", { traitName, error: error instanceof Error ? error : String(error) });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
[]
|
|
100
|
+
);
|
|
101
|
+
const indexTraitRender = useCallback(
|
|
102
|
+
(traitName, content) => {
|
|
103
|
+
traitIndexRef.current.set(traitName, content);
|
|
104
|
+
},
|
|
105
|
+
[]
|
|
106
|
+
);
|
|
107
|
+
const unindexTrait = useCallback((traitName) => {
|
|
108
|
+
traitIndexRef.current.delete(traitName);
|
|
109
|
+
}, []);
|
|
110
|
+
const render = useCallback(
|
|
111
|
+
(config) => {
|
|
112
|
+
const sourceKey = config.sourceTrait ?? DEFAULT_SOURCE_KEY;
|
|
113
|
+
const id = `slot-content-${config.target}-${sourceKey}`;
|
|
114
|
+
const content = {
|
|
115
|
+
id,
|
|
116
|
+
pattern: config.pattern,
|
|
117
|
+
props: config.props ?? {},
|
|
118
|
+
priority: config.priority ?? 0,
|
|
119
|
+
animation: config.animation ?? "fade",
|
|
120
|
+
onDismiss: config.onDismiss,
|
|
121
|
+
sourceTrait: config.sourceTrait,
|
|
122
|
+
slot: config.target,
|
|
123
|
+
transitionEvent: config.transitionEvent,
|
|
124
|
+
fromState: config.fromState,
|
|
125
|
+
entity: config.entity
|
|
126
|
+
};
|
|
127
|
+
if (config.autoDismissMs && config.autoDismissMs > 0) {
|
|
128
|
+
content.autoDismissAt = Date.now() + config.autoDismissMs;
|
|
129
|
+
const prevTimer = timersRef.current.get(id);
|
|
130
|
+
if (prevTimer) clearTimeout(prevTimer);
|
|
131
|
+
const timer = setTimeout(() => {
|
|
132
|
+
setSources((prev) => {
|
|
133
|
+
const slotSources = prev[config.target];
|
|
134
|
+
if (slotSources && slotSources[sourceKey] === content) {
|
|
135
|
+
content.onDismiss?.();
|
|
136
|
+
const next = { ...slotSources };
|
|
137
|
+
delete next[sourceKey];
|
|
138
|
+
const updated = { ...prev, [config.target]: next };
|
|
139
|
+
notifySubscribers(config.target, aggregateSlot(next));
|
|
140
|
+
return updated;
|
|
141
|
+
}
|
|
142
|
+
return prev;
|
|
143
|
+
});
|
|
144
|
+
timersRef.current.delete(id);
|
|
145
|
+
}, config.autoDismissMs);
|
|
146
|
+
timersRef.current.set(id, timer);
|
|
147
|
+
}
|
|
148
|
+
setSources((prev) => {
|
|
149
|
+
const slotSources = prev[config.target] ?? {};
|
|
150
|
+
const existing = slotSources[sourceKey];
|
|
151
|
+
if (existing && existing.priority > content.priority) {
|
|
152
|
+
log.warn("Slot already has higher priority content", {
|
|
153
|
+
slot: config.target,
|
|
154
|
+
sourceKey,
|
|
155
|
+
existingPriority: existing.priority,
|
|
156
|
+
newPriority: content.priority
|
|
157
|
+
});
|
|
158
|
+
return prev;
|
|
159
|
+
}
|
|
160
|
+
const nextSources = {
|
|
161
|
+
...slotSources,
|
|
162
|
+
[sourceKey]: content
|
|
163
|
+
};
|
|
164
|
+
const nextAll = { ...prev, [config.target]: nextSources };
|
|
165
|
+
if (content.sourceTrait) {
|
|
166
|
+
indexTraitRender(content.sourceTrait, content);
|
|
167
|
+
notifyTraitSubscribers(content.sourceTrait, content);
|
|
168
|
+
}
|
|
169
|
+
log.info("slot:written", {
|
|
170
|
+
slot: config.target,
|
|
171
|
+
sourceKey,
|
|
172
|
+
sourceTrait: content.sourceTrait,
|
|
173
|
+
patternType: content.pattern,
|
|
174
|
+
priority: content.priority
|
|
175
|
+
});
|
|
176
|
+
notifySubscribers(config.target, aggregateSlot(nextSources));
|
|
177
|
+
return nextAll;
|
|
178
|
+
});
|
|
179
|
+
return id;
|
|
180
|
+
},
|
|
181
|
+
[notifySubscribers, notifyTraitSubscribers, indexTraitRender]
|
|
182
|
+
);
|
|
183
|
+
const clear = useCallback(
|
|
184
|
+
(slot) => {
|
|
185
|
+
setSources((prev) => {
|
|
186
|
+
const slotSources = prev[slot];
|
|
187
|
+
if (!slotSources || Object.keys(slotSources).length === 0) {
|
|
188
|
+
return prev;
|
|
189
|
+
}
|
|
190
|
+
for (const content of Object.values(slotSources)) {
|
|
191
|
+
const timer = timersRef.current.get(content.id);
|
|
192
|
+
if (timer) {
|
|
193
|
+
clearTimeout(timer);
|
|
194
|
+
timersRef.current.delete(content.id);
|
|
195
|
+
}
|
|
196
|
+
content.onDismiss?.();
|
|
197
|
+
if (content.sourceTrait) {
|
|
198
|
+
unindexTrait(content.sourceTrait);
|
|
199
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
notifySubscribers(slot, null);
|
|
203
|
+
return { ...prev, [slot]: {} };
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
[notifySubscribers, notifyTraitSubscribers, unindexTrait]
|
|
207
|
+
);
|
|
208
|
+
const clearBySource = useCallback(
|
|
209
|
+
(slot, sourceTrait) => {
|
|
210
|
+
const sourceKey = sourceTrait;
|
|
211
|
+
setSources((prev) => {
|
|
212
|
+
const slotSources = prev[slot];
|
|
213
|
+
if (!slotSources || !(sourceKey in slotSources)) {
|
|
214
|
+
log.debug("slot:clear-noop", { slot, sourceTrait, reason: !slotSources ? "no-slot" : "no-source" });
|
|
215
|
+
return prev;
|
|
216
|
+
}
|
|
217
|
+
const content = slotSources[sourceKey];
|
|
218
|
+
const timer = timersRef.current.get(content.id);
|
|
219
|
+
if (timer) {
|
|
220
|
+
clearTimeout(timer);
|
|
221
|
+
timersRef.current.delete(content.id);
|
|
222
|
+
}
|
|
223
|
+
content.onDismiss?.();
|
|
224
|
+
if (content.sourceTrait) {
|
|
225
|
+
unindexTrait(content.sourceTrait);
|
|
226
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
227
|
+
}
|
|
228
|
+
const nextSources = { ...slotSources };
|
|
229
|
+
delete nextSources[sourceKey];
|
|
230
|
+
log.info("slot:cleared", { slot, sourceTrait, lastPatternType: content.pattern });
|
|
231
|
+
notifySubscribers(slot, aggregateSlot(nextSources));
|
|
232
|
+
return { ...prev, [slot]: nextSources };
|
|
233
|
+
});
|
|
234
|
+
},
|
|
235
|
+
[notifySubscribers, notifyTraitSubscribers, unindexTrait]
|
|
236
|
+
);
|
|
237
|
+
const clearById = useCallback(
|
|
238
|
+
(id) => {
|
|
239
|
+
setSources((prev) => {
|
|
240
|
+
for (const slot of ALL_SLOTS) {
|
|
241
|
+
const slotSources = prev[slot];
|
|
242
|
+
if (!slotSources) continue;
|
|
243
|
+
const matchKey = Object.keys(slotSources).find(
|
|
244
|
+
(k) => slotSources[k].id === id
|
|
245
|
+
);
|
|
246
|
+
if (!matchKey) continue;
|
|
247
|
+
const content = slotSources[matchKey];
|
|
248
|
+
const timer = timersRef.current.get(id);
|
|
249
|
+
if (timer) {
|
|
250
|
+
clearTimeout(timer);
|
|
251
|
+
timersRef.current.delete(id);
|
|
252
|
+
}
|
|
253
|
+
content.onDismiss?.();
|
|
254
|
+
if (content.sourceTrait) {
|
|
255
|
+
unindexTrait(content.sourceTrait);
|
|
256
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
257
|
+
}
|
|
258
|
+
const nextSources = { ...slotSources };
|
|
259
|
+
delete nextSources[matchKey];
|
|
260
|
+
notifySubscribers(slot, aggregateSlot(nextSources));
|
|
261
|
+
return { ...prev, [slot]: nextSources };
|
|
262
|
+
}
|
|
263
|
+
return prev;
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
[notifySubscribers, notifyTraitSubscribers, unindexTrait]
|
|
267
|
+
);
|
|
268
|
+
const clearAll = useCallback(() => {
|
|
269
|
+
timersRef.current.forEach((timer) => clearTimeout(timer));
|
|
270
|
+
timersRef.current.clear();
|
|
271
|
+
setSources((prev) => {
|
|
272
|
+
for (const slot of ALL_SLOTS) {
|
|
273
|
+
const slotSources = prev[slot];
|
|
274
|
+
if (!slotSources) continue;
|
|
275
|
+
for (const content of Object.values(slotSources)) {
|
|
276
|
+
content.onDismiss?.();
|
|
277
|
+
if (content.sourceTrait) {
|
|
278
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
notifySubscribers(slot, null);
|
|
282
|
+
}
|
|
283
|
+
return DEFAULT_SOURCES;
|
|
284
|
+
});
|
|
285
|
+
traitIndexRef.current.clear();
|
|
286
|
+
}, [notifySubscribers, notifyTraitSubscribers]);
|
|
287
|
+
const subscribe = useCallback((callback) => {
|
|
288
|
+
subscribersRef.current.add(callback);
|
|
289
|
+
return () => {
|
|
290
|
+
subscribersRef.current.delete(callback);
|
|
291
|
+
};
|
|
292
|
+
}, []);
|
|
293
|
+
const hasContent = useCallback(
|
|
294
|
+
(slot) => slots[slot] !== null,
|
|
295
|
+
[slots]
|
|
296
|
+
);
|
|
297
|
+
const getContent = useCallback(
|
|
298
|
+
(slot) => slots[slot] ?? null,
|
|
299
|
+
[slots]
|
|
300
|
+
);
|
|
301
|
+
const getTraitContent = useCallback(
|
|
302
|
+
(traitName) => traitIndexRef.current.get(traitName) ?? null,
|
|
303
|
+
[]
|
|
304
|
+
);
|
|
305
|
+
const subscribeTrait = useCallback(
|
|
306
|
+
(traitName, callback) => {
|
|
307
|
+
let set = traitSubscribersRef.current.get(traitName);
|
|
308
|
+
if (!set) {
|
|
309
|
+
set = /* @__PURE__ */ new Set();
|
|
310
|
+
traitSubscribersRef.current.set(traitName, set);
|
|
311
|
+
}
|
|
312
|
+
set.add(callback);
|
|
313
|
+
return () => {
|
|
314
|
+
const s = traitSubscribersRef.current.get(traitName);
|
|
315
|
+
if (!s) return;
|
|
316
|
+
s.delete(callback);
|
|
317
|
+
if (s.size === 0) {
|
|
318
|
+
traitSubscribersRef.current.delete(traitName);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
},
|
|
322
|
+
[]
|
|
323
|
+
);
|
|
324
|
+
const updateTraitContent = useCallback(
|
|
325
|
+
(traitName, content) => {
|
|
326
|
+
const id = `slot-content-trait-${traitName}`;
|
|
327
|
+
const fullContent = { ...content, id, sourceTrait: traitName };
|
|
328
|
+
indexTraitRender(traitName, fullContent);
|
|
329
|
+
notifyTraitSubscribers(traitName, fullContent);
|
|
330
|
+
return id;
|
|
331
|
+
},
|
|
332
|
+
[indexTraitRender, notifyTraitSubscribers]
|
|
333
|
+
);
|
|
334
|
+
return {
|
|
335
|
+
slots,
|
|
336
|
+
render,
|
|
337
|
+
clear,
|
|
338
|
+
clearBySource,
|
|
339
|
+
clearById,
|
|
340
|
+
clearAll,
|
|
341
|
+
subscribe,
|
|
342
|
+
hasContent,
|
|
343
|
+
getContent,
|
|
344
|
+
getTraitContent,
|
|
345
|
+
subscribeTrait,
|
|
346
|
+
updateTraitContent
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
var UISlotContext = createContext(null);
|
|
350
|
+
function UISlotProvider({ children }) {
|
|
351
|
+
const slotManager = useUISlotManager();
|
|
352
|
+
const contextValue = useMemo(() => slotManager, [slotManager]);
|
|
353
|
+
return /* @__PURE__ */ jsx(UISlotContext.Provider, { value: contextValue, children });
|
|
354
|
+
}
|
|
355
|
+
function useUISlots() {
|
|
356
|
+
const context = useContext(UISlotContext);
|
|
357
|
+
if (!context) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
"useUISlots must be used within a UISlotProvider. Make sure your component tree is wrapped with <UISlotProvider>."
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
return context;
|
|
363
|
+
}
|
|
364
|
+
function useSlotContent(slot) {
|
|
365
|
+
const { getContent } = useUISlots();
|
|
366
|
+
return getContent(slot);
|
|
367
|
+
}
|
|
368
|
+
function useSlotHasContent(slot) {
|
|
369
|
+
const { hasContent } = useUISlots();
|
|
370
|
+
return hasContent(slot);
|
|
371
|
+
}
|
|
372
|
+
var log2 = createLogger("almadar:ui:theme");
|
|
373
|
+
var BUILT_IN_THEMES = [
|
|
374
|
+
{
|
|
375
|
+
name: "wireframe",
|
|
376
|
+
displayName: "Wireframe",
|
|
377
|
+
hasLightMode: true,
|
|
378
|
+
hasDarkMode: true
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: "minimalist",
|
|
382
|
+
displayName: "Minimalist",
|
|
383
|
+
hasLightMode: true,
|
|
384
|
+
hasDarkMode: true
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: "almadar",
|
|
388
|
+
displayName: "Almadar",
|
|
389
|
+
hasLightMode: true,
|
|
390
|
+
hasDarkMode: true
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
name: "trait-wars",
|
|
394
|
+
displayName: "Trait Wars",
|
|
395
|
+
hasLightMode: false,
|
|
396
|
+
hasDarkMode: true
|
|
397
|
+
},
|
|
398
|
+
// Extended themes
|
|
399
|
+
{
|
|
400
|
+
name: "ocean",
|
|
401
|
+
displayName: "Ocean",
|
|
402
|
+
hasLightMode: true,
|
|
403
|
+
hasDarkMode: true
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
name: "forest",
|
|
407
|
+
displayName: "Forest",
|
|
408
|
+
hasLightMode: true,
|
|
409
|
+
hasDarkMode: true
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: "sunset",
|
|
413
|
+
displayName: "Sunset",
|
|
414
|
+
hasLightMode: true,
|
|
415
|
+
hasDarkMode: true
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
name: "lavender",
|
|
419
|
+
displayName: "Lavender",
|
|
420
|
+
hasLightMode: true,
|
|
421
|
+
hasDarkMode: true
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: "rose",
|
|
425
|
+
displayName: "Rose",
|
|
426
|
+
hasLightMode: true,
|
|
427
|
+
hasDarkMode: true
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
name: "slate",
|
|
431
|
+
displayName: "Slate",
|
|
432
|
+
hasLightMode: true,
|
|
433
|
+
hasDarkMode: true
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
name: "ember",
|
|
437
|
+
displayName: "Ember",
|
|
438
|
+
hasLightMode: true,
|
|
439
|
+
hasDarkMode: true
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: "midnight",
|
|
443
|
+
displayName: "Midnight",
|
|
444
|
+
hasLightMode: true,
|
|
445
|
+
hasDarkMode: true
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: "sand",
|
|
449
|
+
displayName: "Sand",
|
|
450
|
+
hasLightMode: true,
|
|
451
|
+
hasDarkMode: true
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
name: "neon",
|
|
455
|
+
displayName: "Neon",
|
|
456
|
+
hasLightMode: true,
|
|
457
|
+
hasDarkMode: true
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
name: "arctic",
|
|
461
|
+
displayName: "Arctic",
|
|
462
|
+
hasLightMode: true,
|
|
463
|
+
hasDarkMode: true
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: "copper",
|
|
467
|
+
displayName: "Copper",
|
|
468
|
+
hasLightMode: true,
|
|
469
|
+
hasDarkMode: true
|
|
470
|
+
},
|
|
471
|
+
// Layer 1 skin axes — truly-unique themes (compact tech / editorial / brutalist dense / display-heavy / touch-first)
|
|
472
|
+
{
|
|
473
|
+
name: "prism",
|
|
474
|
+
displayName: "Prism",
|
|
475
|
+
hasLightMode: true,
|
|
476
|
+
hasDarkMode: true
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
name: "gazette",
|
|
480
|
+
displayName: "Gazette",
|
|
481
|
+
hasLightMode: true,
|
|
482
|
+
hasDarkMode: true
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: "terminal",
|
|
486
|
+
displayName: "Terminal",
|
|
487
|
+
hasLightMode: true,
|
|
488
|
+
hasDarkMode: true
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
name: "atelier",
|
|
492
|
+
displayName: "Atelier",
|
|
493
|
+
hasLightMode: true,
|
|
494
|
+
hasDarkMode: true
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
name: "kiosk",
|
|
498
|
+
displayName: "Kiosk",
|
|
499
|
+
hasLightMode: true,
|
|
500
|
+
hasDarkMode: true
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: "linear-clean",
|
|
504
|
+
displayName: "Linear Clean",
|
|
505
|
+
hasLightMode: true,
|
|
506
|
+
hasDarkMode: true
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
name: "notion-editorial",
|
|
510
|
+
displayName: "Notion Editorial",
|
|
511
|
+
hasLightMode: true,
|
|
512
|
+
hasDarkMode: true
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
name: "bloomberg-dense",
|
|
516
|
+
displayName: "Bloomberg Dense",
|
|
517
|
+
hasLightMode: true,
|
|
518
|
+
hasDarkMode: true
|
|
519
|
+
}
|
|
520
|
+
];
|
|
521
|
+
var ThemeContext = createContext(void 0);
|
|
522
|
+
var THEME_STORAGE_KEY = "theme";
|
|
523
|
+
var MODE_STORAGE_KEY = "theme-mode";
|
|
524
|
+
function getSystemMode() {
|
|
525
|
+
if (typeof window === "undefined") return "light";
|
|
526
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
527
|
+
}
|
|
528
|
+
function resolveMode(mode) {
|
|
529
|
+
if (mode === "system") {
|
|
530
|
+
return getSystemMode();
|
|
531
|
+
}
|
|
532
|
+
return mode;
|
|
533
|
+
}
|
|
534
|
+
var ThemeProvider = ({
|
|
535
|
+
children,
|
|
536
|
+
themes = [],
|
|
537
|
+
defaultTheme = "wireframe",
|
|
538
|
+
defaultMode = "system",
|
|
539
|
+
targetRef
|
|
540
|
+
}) => {
|
|
541
|
+
const availableThemes = useMemo(() => {
|
|
542
|
+
const themeMap = /* @__PURE__ */ new Map();
|
|
543
|
+
BUILT_IN_THEMES.forEach((t) => themeMap.set(t.name, t));
|
|
544
|
+
themes.forEach((t) => themeMap.set(t.name, t));
|
|
545
|
+
return Array.from(themeMap.values());
|
|
546
|
+
}, [themes]);
|
|
547
|
+
const isScoped = !!targetRef;
|
|
548
|
+
const [theme, setThemeState] = useState(() => {
|
|
549
|
+
if (isScoped || typeof window === "undefined") return defaultTheme;
|
|
550
|
+
const stored = localStorage.getItem(THEME_STORAGE_KEY);
|
|
551
|
+
const validThemes = [
|
|
552
|
+
...BUILT_IN_THEMES.map((t) => t.name),
|
|
553
|
+
...themes.map((t) => t.name)
|
|
554
|
+
];
|
|
555
|
+
if (stored && validThemes.includes(stored)) {
|
|
556
|
+
return stored;
|
|
557
|
+
}
|
|
558
|
+
return defaultTheme;
|
|
559
|
+
});
|
|
560
|
+
const [mode, setModeState] = useState(() => {
|
|
561
|
+
if (isScoped || typeof window === "undefined") return defaultMode;
|
|
562
|
+
const stored = localStorage.getItem(MODE_STORAGE_KEY);
|
|
563
|
+
if (stored === "light" || stored === "dark" || stored === "system") {
|
|
564
|
+
return stored;
|
|
565
|
+
}
|
|
566
|
+
return defaultMode;
|
|
567
|
+
});
|
|
568
|
+
const [resolvedMode, setResolvedMode] = useState(
|
|
569
|
+
() => resolveMode(mode)
|
|
570
|
+
);
|
|
571
|
+
const appliedTheme = useMemo(
|
|
572
|
+
() => `${theme}-${resolvedMode}`,
|
|
573
|
+
[theme, resolvedMode]
|
|
574
|
+
);
|
|
575
|
+
useEffect(() => {
|
|
576
|
+
const updateResolved = () => {
|
|
577
|
+
setResolvedMode(resolveMode(mode));
|
|
578
|
+
};
|
|
579
|
+
updateResolved();
|
|
580
|
+
if (mode === "system") {
|
|
581
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
582
|
+
const handleChange = () => updateResolved();
|
|
583
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
584
|
+
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
585
|
+
}
|
|
586
|
+
return void 0;
|
|
587
|
+
}, [mode]);
|
|
588
|
+
useEffect(() => {
|
|
589
|
+
if (isScoped) {
|
|
590
|
+
if (targetRef?.current) {
|
|
591
|
+
targetRef.current.setAttribute("data-theme", appliedTheme);
|
|
592
|
+
targetRef.current.classList.remove("light", "dark");
|
|
593
|
+
targetRef.current.classList.add(resolvedMode);
|
|
594
|
+
}
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const root = document.documentElement;
|
|
598
|
+
root.setAttribute("data-theme", appliedTheme);
|
|
599
|
+
root.classList.remove("light", "dark");
|
|
600
|
+
root.classList.add(resolvedMode);
|
|
601
|
+
}, [appliedTheme, resolvedMode, targetRef, isScoped]);
|
|
602
|
+
const setTheme = useCallback(
|
|
603
|
+
(newTheme) => {
|
|
604
|
+
const validTheme = availableThemes.find((t) => t.name === newTheme);
|
|
605
|
+
if (validTheme) {
|
|
606
|
+
setThemeState(newTheme);
|
|
607
|
+
if (!isScoped && typeof window !== "undefined") {
|
|
608
|
+
localStorage.setItem(THEME_STORAGE_KEY, newTheme);
|
|
609
|
+
}
|
|
610
|
+
} else {
|
|
611
|
+
log2.warn("Theme not found", {
|
|
612
|
+
theme: newTheme,
|
|
613
|
+
available: availableThemes.map((t) => t.name)
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
[availableThemes]
|
|
618
|
+
);
|
|
619
|
+
const setMode = useCallback((newMode) => {
|
|
620
|
+
setModeState(newMode);
|
|
621
|
+
if (!isScoped && typeof window !== "undefined") {
|
|
622
|
+
localStorage.setItem(MODE_STORAGE_KEY, newMode);
|
|
623
|
+
}
|
|
624
|
+
}, []);
|
|
625
|
+
const toggleMode = useCallback(() => {
|
|
626
|
+
const newMode = resolvedMode === "dark" ? "light" : "dark";
|
|
627
|
+
setMode(newMode);
|
|
628
|
+
}, [resolvedMode, setMode]);
|
|
629
|
+
const contextValue = useMemo(
|
|
630
|
+
() => ({
|
|
631
|
+
theme,
|
|
632
|
+
mode,
|
|
633
|
+
resolvedMode,
|
|
634
|
+
setTheme,
|
|
635
|
+
setMode,
|
|
636
|
+
toggleMode,
|
|
637
|
+
availableThemes,
|
|
638
|
+
appliedTheme
|
|
639
|
+
}),
|
|
640
|
+
[
|
|
641
|
+
theme,
|
|
642
|
+
mode,
|
|
643
|
+
resolvedMode,
|
|
644
|
+
setTheme,
|
|
645
|
+
setMode,
|
|
646
|
+
toggleMode,
|
|
647
|
+
availableThemes,
|
|
648
|
+
appliedTheme
|
|
649
|
+
]
|
|
650
|
+
);
|
|
651
|
+
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: contextValue, children });
|
|
652
|
+
};
|
|
653
|
+
function useTheme() {
|
|
654
|
+
const context = useContext(ThemeContext);
|
|
655
|
+
if (context === void 0) {
|
|
656
|
+
return {
|
|
657
|
+
theme: "wireframe",
|
|
658
|
+
mode: "light",
|
|
659
|
+
resolvedMode: "light",
|
|
660
|
+
setTheme: () => {
|
|
661
|
+
},
|
|
662
|
+
setMode: () => {
|
|
663
|
+
},
|
|
664
|
+
toggleMode: () => {
|
|
665
|
+
},
|
|
666
|
+
availableThemes: BUILT_IN_THEMES,
|
|
667
|
+
appliedTheme: "wireframe-light"
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
return context;
|
|
671
|
+
}
|
|
672
|
+
var ThemeContext_default = ThemeContext;
|
|
2
673
|
|
|
3
674
|
// lib/themeTokens.ts
|
|
4
675
|
function themeTokensToCssVars(tokens, mode = "light", darkVariant) {
|
|
@@ -215,5 +886,14 @@ function resolveThemeForRuntime(theme) {
|
|
|
215
886
|
if (typeof theme === "string") return void 0;
|
|
216
887
|
return theme;
|
|
217
888
|
}
|
|
889
|
+
var DesignThemeProvider = ThemeProvider$1;
|
|
890
|
+
function useDesignTheme() {
|
|
891
|
+
const { theme, setTheme, availableThemes } = useTheme$1();
|
|
892
|
+
return {
|
|
893
|
+
designTheme: theme,
|
|
894
|
+
setDesignTheme: setTheme,
|
|
895
|
+
availableThemes: availableThemes.map((t) => t.name)
|
|
896
|
+
};
|
|
897
|
+
}
|
|
218
898
|
|
|
219
|
-
export { resolveThemeForRuntime, themeTokensToCssVars };
|
|
899
|
+
export { BUILT_IN_THEMES, DesignThemeProvider, ThemeContext_default as ThemeContext, ThemeProvider, UISlotContext, UISlotProvider, resolveThemeForRuntime, themeTokensToCssVars, useDesignTheme, useSlotContent, useSlotHasContent, useTheme, useUISlots };
|