@xenide-io/the-old-ui-theme 0.5.0 → 0.5.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/suite.d.mts +2 -2
- package/dist/suite.d.ts +2 -2
- package/dist/suite.js +98 -33
- package/dist/suite.mjs +98 -33
- package/package.json +2 -2
- package/src/styles/themes.css +150 -1
- package/src/suite/components/ai-panel.test.ts +38 -12
- package/src/suite/components/ai-panel.tsx +108 -39
- package/src/suite/icons/app-accents.ts +10 -1
- package/src/suite/lib/apps.ts +12 -1
package/dist/suite.d.mts
CHANGED
|
@@ -363,7 +363,7 @@ declare function SuiteBottomNav({ items, ariaLabel, dataTest, className, }: {
|
|
|
363
363
|
* Slugs double as accent keys (see icons/app-accents.ts) and data-test
|
|
364
364
|
* suffixes (`switch-app-${slug}`, `suite-nav-${slug}`).
|
|
365
365
|
*/
|
|
366
|
-
type SuiteAppSlug = 'shellstack' | 'tides' | 'turtletime' | 'kraken';
|
|
366
|
+
type SuiteAppSlug = 'shellstack' | 'tides' | 'turtletime' | 'kraken' | 'nakama';
|
|
367
367
|
interface SuiteAppDefinition {
|
|
368
368
|
slug: SuiteAppSlug;
|
|
369
369
|
name: string;
|
|
@@ -547,7 +547,7 @@ declare const SUITE_ICON_NAMES: SuiteIconName[];
|
|
|
547
547
|
* `onTileAccent` drive `<SuiteAppIcon>` (the programmatic favicon-style
|
|
548
548
|
* tile + glyph mark).
|
|
549
549
|
*/
|
|
550
|
-
type SuiteAccentSlug = 'turtletime' | 'tides' | 'kraken' | 'shellstack' | 'crew';
|
|
550
|
+
type SuiteAccentSlug = 'turtletime' | 'tides' | 'kraken' | 'shellstack' | 'crew' | 'nakama';
|
|
551
551
|
interface SuiteAppAccent {
|
|
552
552
|
/** Display name (default aria label for app marks). */
|
|
553
553
|
label: string;
|
package/dist/suite.d.ts
CHANGED
|
@@ -363,7 +363,7 @@ declare function SuiteBottomNav({ items, ariaLabel, dataTest, className, }: {
|
|
|
363
363
|
* Slugs double as accent keys (see icons/app-accents.ts) and data-test
|
|
364
364
|
* suffixes (`switch-app-${slug}`, `suite-nav-${slug}`).
|
|
365
365
|
*/
|
|
366
|
-
type SuiteAppSlug = 'shellstack' | 'tides' | 'turtletime' | 'kraken';
|
|
366
|
+
type SuiteAppSlug = 'shellstack' | 'tides' | 'turtletime' | 'kraken' | 'nakama';
|
|
367
367
|
interface SuiteAppDefinition {
|
|
368
368
|
slug: SuiteAppSlug;
|
|
369
369
|
name: string;
|
|
@@ -547,7 +547,7 @@ declare const SUITE_ICON_NAMES: SuiteIconName[];
|
|
|
547
547
|
* `onTileAccent` drive `<SuiteAppIcon>` (the programmatic favicon-style
|
|
548
548
|
* tile + glyph mark).
|
|
549
549
|
*/
|
|
550
|
-
type SuiteAccentSlug = 'turtletime' | 'tides' | 'kraken' | 'shellstack' | 'crew';
|
|
550
|
+
type SuiteAccentSlug = 'turtletime' | 'tides' | 'kraken' | 'shellstack' | 'crew' | 'nakama';
|
|
551
551
|
interface SuiteAppAccent {
|
|
552
552
|
/** Display name (default aria label for app marks). */
|
|
553
553
|
label: string;
|
package/dist/suite.js
CHANGED
|
@@ -383,6 +383,13 @@ var APP_ACCENTS = {
|
|
|
383
383
|
tile: "#155e75",
|
|
384
384
|
onTile: "#ffffff",
|
|
385
385
|
onTileAccent: "#a5f3fc"
|
|
386
|
+
},
|
|
387
|
+
nakama: {
|
|
388
|
+
label: "Nakama",
|
|
389
|
+
accent: "#0891b2",
|
|
390
|
+
tile: "#155e75",
|
|
391
|
+
onTile: "#ffffff",
|
|
392
|
+
onTileAccent: "#a5f3fc"
|
|
386
393
|
}
|
|
387
394
|
};
|
|
388
395
|
var APP_GLYPHS = {
|
|
@@ -390,7 +397,8 @@ var APP_GLYPHS = {
|
|
|
390
397
|
tides: "kanban-wave",
|
|
391
398
|
kraken: "squid-doc",
|
|
392
399
|
shellstack: "stack-hex",
|
|
393
|
-
crew: "crew-bot"
|
|
400
|
+
crew: "crew-bot",
|
|
401
|
+
nakama: "crew-bot"
|
|
394
402
|
};
|
|
395
403
|
|
|
396
404
|
// src/suite/icons/glyph-parts.tsx
|
|
@@ -1334,6 +1342,15 @@ var SUITE_APPS2 = [
|
|
|
1334
1342
|
landing: "/today",
|
|
1335
1343
|
baseUrlEnv: "NEXT_PUBLIC_KRAKEN_URL",
|
|
1336
1344
|
baseUrlFallback: "http://localhost:3003"
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
slug: "nakama",
|
|
1348
|
+
name: "Nakama",
|
|
1349
|
+
description: "AI teammates",
|
|
1350
|
+
icon: "/nakama-icon.svg",
|
|
1351
|
+
landing: "/today",
|
|
1352
|
+
baseUrlEnv: "NEXT_PUBLIC_NAKAMA_URL",
|
|
1353
|
+
baseUrlFallback: "http://localhost:3004"
|
|
1337
1354
|
}
|
|
1338
1355
|
];
|
|
1339
1356
|
var SUITE_APP_MAP = Object.fromEntries(SUITE_APPS2.map((app) => [app.slug, app]));
|
|
@@ -1346,6 +1363,7 @@ var SOURCE_APP_SLUG = {
|
|
|
1346
1363
|
turtletime: "turtletime",
|
|
1347
1364
|
tides: "tides",
|
|
1348
1365
|
kraken: "kraken",
|
|
1366
|
+
nakama: "nakama",
|
|
1349
1367
|
portal: "shellstack",
|
|
1350
1368
|
shellstack: "shellstack"
|
|
1351
1369
|
};
|
|
@@ -2389,9 +2407,7 @@ init_theme_provider();
|
|
|
2389
2407
|
var import_react10 = require("react");
|
|
2390
2408
|
var import_iconoir_react8 = require("iconoir-react");
|
|
2391
2409
|
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
2392
|
-
var SuiteAiBlockNoteMessage2 = (0, import_react10.lazy)(
|
|
2393
|
-
() => Promise.resolve().then(() => (init_ai_message_blocknote(), ai_message_blocknote_exports))
|
|
2394
|
-
);
|
|
2410
|
+
var SuiteAiBlockNoteMessage2 = (0, import_react10.lazy)(() => Promise.resolve().then(() => (init_ai_message_blocknote(), ai_message_blocknote_exports)));
|
|
2395
2411
|
var SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
|
|
2396
2412
|
function openSuiteAskAi(detail) {
|
|
2397
2413
|
if (typeof window === "undefined") return;
|
|
@@ -2403,7 +2419,7 @@ function openSuiteAskAi(detail) {
|
|
|
2403
2419
|
}
|
|
2404
2420
|
var LAUNCHER_EDGE_GAP = 12;
|
|
2405
2421
|
var LAUNCHER_STORAGE_KEY = "suite-ask-ai-launcher-position";
|
|
2406
|
-
function resolveSuiteAiLauncherPosition(
|
|
2422
|
+
function resolveSuiteAiLauncherPosition(edge, desiredX, desiredY, viewportWidth, viewportHeight, launcherWidth, launcherHeight) {
|
|
2407
2423
|
const maxX = Math.max(
|
|
2408
2424
|
LAUNCHER_EDGE_GAP,
|
|
2409
2425
|
viewportWidth - launcherWidth - LAUNCHER_EDGE_GAP
|
|
@@ -2412,10 +2428,36 @@ function resolveSuiteAiLauncherPosition(side, desiredY, viewportWidth, viewportH
|
|
|
2412
2428
|
LAUNCHER_EDGE_GAP,
|
|
2413
2429
|
viewportHeight - launcherHeight - LAUNCHER_EDGE_GAP
|
|
2414
2430
|
);
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2431
|
+
const clampX = (x) => Math.min(Math.max(x, LAUNCHER_EDGE_GAP), maxX);
|
|
2432
|
+
const clampY = (y) => Math.min(Math.max(y, LAUNCHER_EDGE_GAP), maxY);
|
|
2433
|
+
switch (edge) {
|
|
2434
|
+
case "left":
|
|
2435
|
+
return { x: LAUNCHER_EDGE_GAP, y: clampY(desiredY) };
|
|
2436
|
+
case "right":
|
|
2437
|
+
return { x: maxX, y: clampY(desiredY) };
|
|
2438
|
+
case "top":
|
|
2439
|
+
return { x: clampX(desiredX), y: LAUNCHER_EDGE_GAP };
|
|
2440
|
+
case "bottom":
|
|
2441
|
+
return { x: clampX(desiredX), y: maxY };
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
function resolveSuiteAiNearestEdge(x, y, width, height, viewportWidth, viewportHeight) {
|
|
2445
|
+
const centerX = x + width / 2;
|
|
2446
|
+
const centerY = y + height / 2;
|
|
2447
|
+
const candidates = [
|
|
2448
|
+
{ edge: "left", distance: centerX },
|
|
2449
|
+
{ edge: "right", distance: viewportWidth - centerX },
|
|
2450
|
+
{ edge: "top", distance: centerY },
|
|
2451
|
+
{ edge: "bottom", distance: viewportHeight - centerY }
|
|
2452
|
+
];
|
|
2453
|
+
return candidates.reduce(
|
|
2454
|
+
(nearest, candidate) => candidate.distance < nearest.distance ? candidate : nearest
|
|
2455
|
+
).edge;
|
|
2456
|
+
}
|
|
2457
|
+
function resolveSuiteAiPanelSide(edge, x, viewportWidth) {
|
|
2458
|
+
if (edge === "left") return "left";
|
|
2459
|
+
if (edge === "right") return "right";
|
|
2460
|
+
return x < viewportWidth / 2 ? "left" : "right";
|
|
2419
2461
|
}
|
|
2420
2462
|
function SuiteAiPanel({
|
|
2421
2463
|
presets = [],
|
|
@@ -2436,6 +2478,7 @@ function SuiteAiPanel({
|
|
|
2436
2478
|
const [error, setError] = (0, import_react10.useState)("");
|
|
2437
2479
|
const [copiedId, setCopiedId] = (0, import_react10.useState)(null);
|
|
2438
2480
|
const [launcherSide, setLauncherSide] = (0, import_react10.useState)("right");
|
|
2481
|
+
const [panelSide, setPanelSide] = (0, import_react10.useState)("right");
|
|
2439
2482
|
const [launcherPosition, setLauncherPosition] = (0, import_react10.useState)(null);
|
|
2440
2483
|
const [draggingLauncher, setDraggingLauncher] = (0, import_react10.useState)(false);
|
|
2441
2484
|
const scrollerRef = (0, import_react10.useRef)(null);
|
|
@@ -2449,25 +2492,28 @@ function SuiteAiPanel({
|
|
|
2449
2492
|
const launcher = launcherRef.current;
|
|
2450
2493
|
if (!launcher) return;
|
|
2451
2494
|
let side = "right";
|
|
2495
|
+
let desiredX = LAUNCHER_EDGE_GAP;
|
|
2452
2496
|
let desiredY = window.innerHeight - launcher.offsetHeight - 20;
|
|
2453
2497
|
try {
|
|
2454
2498
|
const stored = JSON.parse(
|
|
2455
2499
|
localStorage.getItem(LAUNCHER_STORAGE_KEY) || "null"
|
|
2456
2500
|
);
|
|
2457
|
-
if ((stored == null ? void 0 : stored.side) === "
|
|
2501
|
+
if ((stored == null ? void 0 : stored.side) === "top" || (stored == null ? void 0 : stored.side) === "right" || (stored == null ? void 0 : stored.side) === "bottom" || (stored == null ? void 0 : stored.side) === "left")
|
|
2458
2502
|
side = stored.side;
|
|
2503
|
+
if (typeof (stored == null ? void 0 : stored.x) === "number") desiredX = stored.x;
|
|
2459
2504
|
if (typeof (stored == null ? void 0 : stored.y) === "number") desiredY = stored.y;
|
|
2460
2505
|
} catch (e) {
|
|
2461
2506
|
}
|
|
2462
2507
|
launcherSideRef.current = side;
|
|
2463
2508
|
setLauncherSide(side);
|
|
2464
2509
|
const placeLauncher = () => {
|
|
2465
|
-
var _a, _b;
|
|
2510
|
+
var _a, _b, _c, _d;
|
|
2466
2511
|
const element = launcherRef.current;
|
|
2467
2512
|
if (!element) return;
|
|
2468
2513
|
const next = resolveSuiteAiLauncherPosition(
|
|
2469
2514
|
launcherSideRef.current,
|
|
2470
|
-
(_b = (_a = launcherPositionRef.current) == null ? void 0 : _a.
|
|
2515
|
+
(_b = (_a = launcherPositionRef.current) == null ? void 0 : _a.x) != null ? _b : desiredX,
|
|
2516
|
+
(_d = (_c = launcherPositionRef.current) == null ? void 0 : _c.y) != null ? _d : desiredY,
|
|
2471
2517
|
window.innerWidth,
|
|
2472
2518
|
window.innerHeight,
|
|
2473
2519
|
element.offsetWidth,
|
|
@@ -2475,6 +2521,13 @@ function SuiteAiPanel({
|
|
|
2475
2521
|
);
|
|
2476
2522
|
launcherPositionRef.current = next;
|
|
2477
2523
|
setLauncherPosition(next);
|
|
2524
|
+
setPanelSide(
|
|
2525
|
+
resolveSuiteAiPanelSide(
|
|
2526
|
+
launcherSideRef.current,
|
|
2527
|
+
next.x,
|
|
2528
|
+
window.innerWidth
|
|
2529
|
+
)
|
|
2530
|
+
);
|
|
2478
2531
|
};
|
|
2479
2532
|
placeLauncher();
|
|
2480
2533
|
window.addEventListener("resize", placeLauncher);
|
|
@@ -2575,31 +2628,33 @@ function SuiteAiPanel({
|
|
|
2575
2628
|
} catch (e) {
|
|
2576
2629
|
}
|
|
2577
2630
|
}
|
|
2578
|
-
function saveLauncherPosition(
|
|
2631
|
+
function saveLauncherPosition(edge, position) {
|
|
2579
2632
|
try {
|
|
2580
2633
|
localStorage.setItem(
|
|
2581
2634
|
LAUNCHER_STORAGE_KEY,
|
|
2582
|
-
JSON.stringify({ side, y: position.y })
|
|
2635
|
+
JSON.stringify({ side: edge, x: position.x, y: position.y })
|
|
2583
2636
|
);
|
|
2584
2637
|
} catch (e) {
|
|
2585
2638
|
}
|
|
2586
2639
|
}
|
|
2587
|
-
function dockLauncher(
|
|
2640
|
+
function dockLauncher(edge, desiredX, desiredY) {
|
|
2588
2641
|
const launcher = launcherRef.current;
|
|
2589
2642
|
if (!launcher) return;
|
|
2590
2643
|
const next = resolveSuiteAiLauncherPosition(
|
|
2591
|
-
|
|
2644
|
+
edge,
|
|
2645
|
+
desiredX,
|
|
2592
2646
|
desiredY,
|
|
2593
2647
|
window.innerWidth,
|
|
2594
2648
|
window.innerHeight,
|
|
2595
2649
|
launcher.offsetWidth,
|
|
2596
2650
|
launcher.offsetHeight
|
|
2597
2651
|
);
|
|
2598
|
-
launcherSideRef.current =
|
|
2652
|
+
launcherSideRef.current = edge;
|
|
2599
2653
|
launcherPositionRef.current = next;
|
|
2600
|
-
setLauncherSide(
|
|
2654
|
+
setLauncherSide(edge);
|
|
2601
2655
|
setLauncherPosition(next);
|
|
2602
|
-
|
|
2656
|
+
setPanelSide(resolveSuiteAiPanelSide(edge, next.x, window.innerWidth));
|
|
2657
|
+
saveLauncherPosition(edge, next);
|
|
2603
2658
|
}
|
|
2604
2659
|
function startLauncherDrag(event) {
|
|
2605
2660
|
if (event.pointerType === "mouse" && event.button !== 0) return;
|
|
@@ -2654,22 +2709,32 @@ function SuiteAiPanel({
|
|
|
2654
2709
|
setDraggingLauncher(false);
|
|
2655
2710
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
2656
2711
|
const currentX = (_b = (_a = launcherPositionRef.current) == null ? void 0 : _a.x) != null ? _b : rect.left;
|
|
2657
|
-
const
|
|
2658
|
-
|
|
2712
|
+
const currentY = (_d = (_c = launcherPositionRef.current) == null ? void 0 : _c.y) != null ? _d : rect.top;
|
|
2713
|
+
const edge = resolveSuiteAiNearestEdge(
|
|
2714
|
+
currentX,
|
|
2715
|
+
currentY,
|
|
2716
|
+
rect.width,
|
|
2717
|
+
rect.height,
|
|
2718
|
+
window.innerWidth,
|
|
2719
|
+
window.innerHeight
|
|
2720
|
+
);
|
|
2721
|
+
dockLauncher(edge, currentX, currentY);
|
|
2659
2722
|
}
|
|
2660
2723
|
function moveLauncherWithKeyboard(event) {
|
|
2661
|
-
var _a, _b;
|
|
2662
|
-
|
|
2663
|
-
|
|
2724
|
+
var _a, _b, _c, _d;
|
|
2725
|
+
const edgeByKey = {
|
|
2726
|
+
ArrowLeft: "left",
|
|
2727
|
+
ArrowRight: "right",
|
|
2728
|
+
ArrowUp: "top",
|
|
2729
|
+
ArrowDown: "bottom"
|
|
2730
|
+
};
|
|
2731
|
+
const edge = edgeByKey[event.key];
|
|
2732
|
+
if (!edge) return;
|
|
2664
2733
|
event.preventDefault();
|
|
2665
|
-
const
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
dockLauncher(
|
|
2670
|
-
launcherSideRef.current,
|
|
2671
|
-
currentY + (event.key === "ArrowUp" ? -24 : 24)
|
|
2672
|
-
);
|
|
2734
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
2735
|
+
const currentX = (_b = (_a = launcherPositionRef.current) == null ? void 0 : _a.x) != null ? _b : rect.left;
|
|
2736
|
+
const currentY = (_d = (_c = launcherPositionRef.current) == null ? void 0 : _c.y) != null ? _d : rect.top;
|
|
2737
|
+
dockLauncher(edge, currentX, currentY);
|
|
2673
2738
|
}
|
|
2674
2739
|
const Icon = BrandIcon != null ? BrandIcon : import_iconoir_react8.Sparks;
|
|
2675
2740
|
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
|
|
@@ -2717,7 +2782,7 @@ function SuiteAiPanel({
|
|
|
2717
2782
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
2718
2783
|
"aside",
|
|
2719
2784
|
{
|
|
2720
|
-
className: `absolute inset-y-0 flex w-full max-w-md flex-col bg-ph-surface shadow-2xl ${
|
|
2785
|
+
className: `absolute inset-y-0 flex w-full max-w-md flex-col bg-ph-surface shadow-2xl ${panelSide === "left" ? "left-0 border-r border-ph-border" : "right-0 border-l border-ph-border"}`,
|
|
2721
2786
|
"data-test": "suite-ask-ai-panel",
|
|
2722
2787
|
children: [
|
|
2723
2788
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center justify-between border-b border-ph-border px-4 py-3", children: [
|
package/dist/suite.mjs
CHANGED
|
@@ -96,6 +96,13 @@ var APP_ACCENTS = {
|
|
|
96
96
|
tile: "#155e75",
|
|
97
97
|
onTile: "#ffffff",
|
|
98
98
|
onTileAccent: "#a5f3fc"
|
|
99
|
+
},
|
|
100
|
+
nakama: {
|
|
101
|
+
label: "Nakama",
|
|
102
|
+
accent: "#0891b2",
|
|
103
|
+
tile: "#155e75",
|
|
104
|
+
onTile: "#ffffff",
|
|
105
|
+
onTileAccent: "#a5f3fc"
|
|
99
106
|
}
|
|
100
107
|
};
|
|
101
108
|
var APP_GLYPHS = {
|
|
@@ -103,7 +110,8 @@ var APP_GLYPHS = {
|
|
|
103
110
|
tides: "kanban-wave",
|
|
104
111
|
kraken: "squid-doc",
|
|
105
112
|
shellstack: "stack-hex",
|
|
106
|
-
crew: "crew-bot"
|
|
113
|
+
crew: "crew-bot",
|
|
114
|
+
nakama: "crew-bot"
|
|
107
115
|
};
|
|
108
116
|
|
|
109
117
|
// src/suite/icons/glyph-parts.tsx
|
|
@@ -1047,6 +1055,15 @@ var SUITE_APPS2 = [
|
|
|
1047
1055
|
landing: "/today",
|
|
1048
1056
|
baseUrlEnv: "NEXT_PUBLIC_KRAKEN_URL",
|
|
1049
1057
|
baseUrlFallback: "http://localhost:3003"
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
slug: "nakama",
|
|
1061
|
+
name: "Nakama",
|
|
1062
|
+
description: "AI teammates",
|
|
1063
|
+
icon: "/nakama-icon.svg",
|
|
1064
|
+
landing: "/today",
|
|
1065
|
+
baseUrlEnv: "NEXT_PUBLIC_NAKAMA_URL",
|
|
1066
|
+
baseUrlFallback: "http://localhost:3004"
|
|
1050
1067
|
}
|
|
1051
1068
|
];
|
|
1052
1069
|
var SUITE_APP_MAP = Object.fromEntries(SUITE_APPS2.map((app) => [app.slug, app]));
|
|
@@ -1059,6 +1076,7 @@ var SOURCE_APP_SLUG = {
|
|
|
1059
1076
|
turtletime: "turtletime",
|
|
1060
1077
|
tides: "tides",
|
|
1061
1078
|
kraken: "kraken",
|
|
1079
|
+
nakama: "nakama",
|
|
1062
1080
|
portal: "shellstack",
|
|
1063
1081
|
shellstack: "shellstack"
|
|
1064
1082
|
};
|
|
@@ -1994,9 +2012,7 @@ import {
|
|
|
1994
2012
|
} from "react";
|
|
1995
2013
|
import { Check as Check2, Copy, Sparks, Xmark as X2 } from "iconoir-react";
|
|
1996
2014
|
import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1997
|
-
var SuiteAiBlockNoteMessage = lazy(
|
|
1998
|
-
() => import("./ai-message-blocknote-4Q4U2T2W.mjs")
|
|
1999
|
-
);
|
|
2015
|
+
var SuiteAiBlockNoteMessage = lazy(() => import("./ai-message-blocknote-4Q4U2T2W.mjs"));
|
|
2000
2016
|
var SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
|
|
2001
2017
|
function openSuiteAskAi(detail) {
|
|
2002
2018
|
if (typeof window === "undefined") return;
|
|
@@ -2008,7 +2024,7 @@ function openSuiteAskAi(detail) {
|
|
|
2008
2024
|
}
|
|
2009
2025
|
var LAUNCHER_EDGE_GAP = 12;
|
|
2010
2026
|
var LAUNCHER_STORAGE_KEY = "suite-ask-ai-launcher-position";
|
|
2011
|
-
function resolveSuiteAiLauncherPosition(
|
|
2027
|
+
function resolveSuiteAiLauncherPosition(edge, desiredX, desiredY, viewportWidth, viewportHeight, launcherWidth, launcherHeight) {
|
|
2012
2028
|
const maxX = Math.max(
|
|
2013
2029
|
LAUNCHER_EDGE_GAP,
|
|
2014
2030
|
viewportWidth - launcherWidth - LAUNCHER_EDGE_GAP
|
|
@@ -2017,10 +2033,36 @@ function resolveSuiteAiLauncherPosition(side, desiredY, viewportWidth, viewportH
|
|
|
2017
2033
|
LAUNCHER_EDGE_GAP,
|
|
2018
2034
|
viewportHeight - launcherHeight - LAUNCHER_EDGE_GAP
|
|
2019
2035
|
);
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2036
|
+
const clampX = (x) => Math.min(Math.max(x, LAUNCHER_EDGE_GAP), maxX);
|
|
2037
|
+
const clampY = (y) => Math.min(Math.max(y, LAUNCHER_EDGE_GAP), maxY);
|
|
2038
|
+
switch (edge) {
|
|
2039
|
+
case "left":
|
|
2040
|
+
return { x: LAUNCHER_EDGE_GAP, y: clampY(desiredY) };
|
|
2041
|
+
case "right":
|
|
2042
|
+
return { x: maxX, y: clampY(desiredY) };
|
|
2043
|
+
case "top":
|
|
2044
|
+
return { x: clampX(desiredX), y: LAUNCHER_EDGE_GAP };
|
|
2045
|
+
case "bottom":
|
|
2046
|
+
return { x: clampX(desiredX), y: maxY };
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
function resolveSuiteAiNearestEdge(x, y, width, height, viewportWidth, viewportHeight) {
|
|
2050
|
+
const centerX = x + width / 2;
|
|
2051
|
+
const centerY = y + height / 2;
|
|
2052
|
+
const candidates = [
|
|
2053
|
+
{ edge: "left", distance: centerX },
|
|
2054
|
+
{ edge: "right", distance: viewportWidth - centerX },
|
|
2055
|
+
{ edge: "top", distance: centerY },
|
|
2056
|
+
{ edge: "bottom", distance: viewportHeight - centerY }
|
|
2057
|
+
];
|
|
2058
|
+
return candidates.reduce(
|
|
2059
|
+
(nearest, candidate) => candidate.distance < nearest.distance ? candidate : nearest
|
|
2060
|
+
).edge;
|
|
2061
|
+
}
|
|
2062
|
+
function resolveSuiteAiPanelSide(edge, x, viewportWidth) {
|
|
2063
|
+
if (edge === "left") return "left";
|
|
2064
|
+
if (edge === "right") return "right";
|
|
2065
|
+
return x < viewportWidth / 2 ? "left" : "right";
|
|
2024
2066
|
}
|
|
2025
2067
|
function SuiteAiPanel({
|
|
2026
2068
|
presets = [],
|
|
@@ -2041,6 +2083,7 @@ function SuiteAiPanel({
|
|
|
2041
2083
|
const [error, setError] = useState7("");
|
|
2042
2084
|
const [copiedId, setCopiedId] = useState7(null);
|
|
2043
2085
|
const [launcherSide, setLauncherSide] = useState7("right");
|
|
2086
|
+
const [panelSide, setPanelSide] = useState7("right");
|
|
2044
2087
|
const [launcherPosition, setLauncherPosition] = useState7(null);
|
|
2045
2088
|
const [draggingLauncher, setDraggingLauncher] = useState7(false);
|
|
2046
2089
|
const scrollerRef = useRef3(null);
|
|
@@ -2054,25 +2097,28 @@ function SuiteAiPanel({
|
|
|
2054
2097
|
const launcher = launcherRef.current;
|
|
2055
2098
|
if (!launcher) return;
|
|
2056
2099
|
let side = "right";
|
|
2100
|
+
let desiredX = LAUNCHER_EDGE_GAP;
|
|
2057
2101
|
let desiredY = window.innerHeight - launcher.offsetHeight - 20;
|
|
2058
2102
|
try {
|
|
2059
2103
|
const stored = JSON.parse(
|
|
2060
2104
|
localStorage.getItem(LAUNCHER_STORAGE_KEY) || "null"
|
|
2061
2105
|
);
|
|
2062
|
-
if ((stored == null ? void 0 : stored.side) === "
|
|
2106
|
+
if ((stored == null ? void 0 : stored.side) === "top" || (stored == null ? void 0 : stored.side) === "right" || (stored == null ? void 0 : stored.side) === "bottom" || (stored == null ? void 0 : stored.side) === "left")
|
|
2063
2107
|
side = stored.side;
|
|
2108
|
+
if (typeof (stored == null ? void 0 : stored.x) === "number") desiredX = stored.x;
|
|
2064
2109
|
if (typeof (stored == null ? void 0 : stored.y) === "number") desiredY = stored.y;
|
|
2065
2110
|
} catch (e) {
|
|
2066
2111
|
}
|
|
2067
2112
|
launcherSideRef.current = side;
|
|
2068
2113
|
setLauncherSide(side);
|
|
2069
2114
|
const placeLauncher = () => {
|
|
2070
|
-
var _a, _b;
|
|
2115
|
+
var _a, _b, _c, _d;
|
|
2071
2116
|
const element = launcherRef.current;
|
|
2072
2117
|
if (!element) return;
|
|
2073
2118
|
const next = resolveSuiteAiLauncherPosition(
|
|
2074
2119
|
launcherSideRef.current,
|
|
2075
|
-
(_b = (_a = launcherPositionRef.current) == null ? void 0 : _a.
|
|
2120
|
+
(_b = (_a = launcherPositionRef.current) == null ? void 0 : _a.x) != null ? _b : desiredX,
|
|
2121
|
+
(_d = (_c = launcherPositionRef.current) == null ? void 0 : _c.y) != null ? _d : desiredY,
|
|
2076
2122
|
window.innerWidth,
|
|
2077
2123
|
window.innerHeight,
|
|
2078
2124
|
element.offsetWidth,
|
|
@@ -2080,6 +2126,13 @@ function SuiteAiPanel({
|
|
|
2080
2126
|
);
|
|
2081
2127
|
launcherPositionRef.current = next;
|
|
2082
2128
|
setLauncherPosition(next);
|
|
2129
|
+
setPanelSide(
|
|
2130
|
+
resolveSuiteAiPanelSide(
|
|
2131
|
+
launcherSideRef.current,
|
|
2132
|
+
next.x,
|
|
2133
|
+
window.innerWidth
|
|
2134
|
+
)
|
|
2135
|
+
);
|
|
2083
2136
|
};
|
|
2084
2137
|
placeLauncher();
|
|
2085
2138
|
window.addEventListener("resize", placeLauncher);
|
|
@@ -2180,31 +2233,33 @@ function SuiteAiPanel({
|
|
|
2180
2233
|
} catch (e) {
|
|
2181
2234
|
}
|
|
2182
2235
|
}
|
|
2183
|
-
function saveLauncherPosition(
|
|
2236
|
+
function saveLauncherPosition(edge, position) {
|
|
2184
2237
|
try {
|
|
2185
2238
|
localStorage.setItem(
|
|
2186
2239
|
LAUNCHER_STORAGE_KEY,
|
|
2187
|
-
JSON.stringify({ side, y: position.y })
|
|
2240
|
+
JSON.stringify({ side: edge, x: position.x, y: position.y })
|
|
2188
2241
|
);
|
|
2189
2242
|
} catch (e) {
|
|
2190
2243
|
}
|
|
2191
2244
|
}
|
|
2192
|
-
function dockLauncher(
|
|
2245
|
+
function dockLauncher(edge, desiredX, desiredY) {
|
|
2193
2246
|
const launcher = launcherRef.current;
|
|
2194
2247
|
if (!launcher) return;
|
|
2195
2248
|
const next = resolveSuiteAiLauncherPosition(
|
|
2196
|
-
|
|
2249
|
+
edge,
|
|
2250
|
+
desiredX,
|
|
2197
2251
|
desiredY,
|
|
2198
2252
|
window.innerWidth,
|
|
2199
2253
|
window.innerHeight,
|
|
2200
2254
|
launcher.offsetWidth,
|
|
2201
2255
|
launcher.offsetHeight
|
|
2202
2256
|
);
|
|
2203
|
-
launcherSideRef.current =
|
|
2257
|
+
launcherSideRef.current = edge;
|
|
2204
2258
|
launcherPositionRef.current = next;
|
|
2205
|
-
setLauncherSide(
|
|
2259
|
+
setLauncherSide(edge);
|
|
2206
2260
|
setLauncherPosition(next);
|
|
2207
|
-
|
|
2261
|
+
setPanelSide(resolveSuiteAiPanelSide(edge, next.x, window.innerWidth));
|
|
2262
|
+
saveLauncherPosition(edge, next);
|
|
2208
2263
|
}
|
|
2209
2264
|
function startLauncherDrag(event) {
|
|
2210
2265
|
if (event.pointerType === "mouse" && event.button !== 0) return;
|
|
@@ -2259,22 +2314,32 @@ function SuiteAiPanel({
|
|
|
2259
2314
|
setDraggingLauncher(false);
|
|
2260
2315
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
2261
2316
|
const currentX = (_b = (_a = launcherPositionRef.current) == null ? void 0 : _a.x) != null ? _b : rect.left;
|
|
2262
|
-
const
|
|
2263
|
-
|
|
2317
|
+
const currentY = (_d = (_c = launcherPositionRef.current) == null ? void 0 : _c.y) != null ? _d : rect.top;
|
|
2318
|
+
const edge = resolveSuiteAiNearestEdge(
|
|
2319
|
+
currentX,
|
|
2320
|
+
currentY,
|
|
2321
|
+
rect.width,
|
|
2322
|
+
rect.height,
|
|
2323
|
+
window.innerWidth,
|
|
2324
|
+
window.innerHeight
|
|
2325
|
+
);
|
|
2326
|
+
dockLauncher(edge, currentX, currentY);
|
|
2264
2327
|
}
|
|
2265
2328
|
function moveLauncherWithKeyboard(event) {
|
|
2266
|
-
var _a, _b;
|
|
2267
|
-
|
|
2268
|
-
|
|
2329
|
+
var _a, _b, _c, _d;
|
|
2330
|
+
const edgeByKey = {
|
|
2331
|
+
ArrowLeft: "left",
|
|
2332
|
+
ArrowRight: "right",
|
|
2333
|
+
ArrowUp: "top",
|
|
2334
|
+
ArrowDown: "bottom"
|
|
2335
|
+
};
|
|
2336
|
+
const edge = edgeByKey[event.key];
|
|
2337
|
+
if (!edge) return;
|
|
2269
2338
|
event.preventDefault();
|
|
2270
|
-
const
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
dockLauncher(
|
|
2275
|
-
launcherSideRef.current,
|
|
2276
|
-
currentY + (event.key === "ArrowUp" ? -24 : 24)
|
|
2277
|
-
);
|
|
2339
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
2340
|
+
const currentX = (_b = (_a = launcherPositionRef.current) == null ? void 0 : _a.x) != null ? _b : rect.left;
|
|
2341
|
+
const currentY = (_d = (_c = launcherPositionRef.current) == null ? void 0 : _c.y) != null ? _d : rect.top;
|
|
2342
|
+
dockLauncher(edge, currentX, currentY);
|
|
2278
2343
|
}
|
|
2279
2344
|
const Icon = BrandIcon != null ? BrandIcon : Sparks;
|
|
2280
2345
|
return /* @__PURE__ */ jsxs15(Fragment5, { children: [
|
|
@@ -2322,7 +2387,7 @@ function SuiteAiPanel({
|
|
|
2322
2387
|
/* @__PURE__ */ jsxs15(
|
|
2323
2388
|
"aside",
|
|
2324
2389
|
{
|
|
2325
|
-
className: `absolute inset-y-0 flex w-full max-w-md flex-col bg-ph-surface shadow-2xl ${
|
|
2390
|
+
className: `absolute inset-y-0 flex w-full max-w-md flex-col bg-ph-surface shadow-2xl ${panelSide === "left" ? "left-0 border-r border-ph-border" : "right-0 border-l border-ph-border"}`,
|
|
2326
2391
|
"data-test": "suite-ask-ai-panel",
|
|
2327
2392
|
children: [
|
|
2328
2393
|
/* @__PURE__ */ jsxs15("div", { className: "flex items-center justify-between border-b border-ph-border px-4 py-3", children: [
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xenide-io/the-old-ui-theme",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "Props-driven React components and theme tokens inspired by classic interface design
|
|
3
|
+
"version": "0.5.2",
|
|
4
|
+
"description": "Props-driven React components and theme tokens inspired by classic interface design \u2014 optimized for Next.js apps and internal tools.",
|
|
5
5
|
"author": "Xenide",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/xenide-io/the-old-ui-theme",
|
package/src/styles/themes.css
CHANGED
|
@@ -1764,6 +1764,154 @@
|
|
|
1764
1764
|
--ph-select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='none' stroke='%23929292' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 24 24'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
|
1765
1765
|
}
|
|
1766
1766
|
|
|
1767
|
+
/* Nakama Light — cyan-accented bright suite palette for the AI teammate app. */
|
|
1768
|
+
[data-theme="nakama-light"] {
|
|
1769
|
+
color-scheme: light;
|
|
1770
|
+
|
|
1771
|
+
--ph-canvas: #f6f8f9;
|
|
1772
|
+
--ph-surface: #ffffff;
|
|
1773
|
+
--ph-muted: #eef2f4;
|
|
1774
|
+
--ph-toolbar: #e6ebee;
|
|
1775
|
+
--ph-surface-inset: #eef2f4;
|
|
1776
|
+
--ph-surface-raised: #ffffff;
|
|
1777
|
+
--ph-surface-overlay: #ffffff;
|
|
1778
|
+
--ph-border: #dfe5e8;
|
|
1779
|
+
--ph-border-subtle: #eaeff1;
|
|
1780
|
+
--ph-border-strong: #a5b0b7;
|
|
1781
|
+
|
|
1782
|
+
--ph-text-primary: #17242c;
|
|
1783
|
+
--ph-text-secondary: #42525c;
|
|
1784
|
+
--ph-text-tertiary: #5f6f7a;
|
|
1785
|
+
|
|
1786
|
+
--ph-accent: #0891b2;
|
|
1787
|
+
--ph-accent-hover: #0e7490;
|
|
1788
|
+
--ph-blue: #437aff;
|
|
1789
|
+
--ph-violet: #8b5cf6;
|
|
1790
|
+
--ph-purple: #7c3aed;
|
|
1791
|
+
|
|
1792
|
+
--ph-success: #15803d;
|
|
1793
|
+
--ph-warning: #b26a00;
|
|
1794
|
+
--ph-danger: #c7463a;
|
|
1795
|
+
--ph-info: #1769aa;
|
|
1796
|
+
--ph-on-accent: #ffffff;
|
|
1797
|
+
--ph-on-violet: #ffffff;
|
|
1798
|
+
--ph-on-success: #ffffff;
|
|
1799
|
+
--ph-on-warning: #211606;
|
|
1800
|
+
--ph-on-danger: #ffffff;
|
|
1801
|
+
--ph-on-info: #ffffff;
|
|
1802
|
+
--ph-focus-ring: #0891b2;
|
|
1803
|
+
--ph-selected-bg: #e6f6fa;
|
|
1804
|
+
--ph-selected-text: #0b5569;
|
|
1805
|
+
--ph-selected-border: #67c6dd;
|
|
1806
|
+
|
|
1807
|
+
--ph-text-3000: #ffffff;
|
|
1808
|
+
--ph-primary-button-face: #0891b2;
|
|
1809
|
+
--ph-primary-button-border: #0b728c;
|
|
1810
|
+
--ph-primary-button-border-hover: #0e7490;
|
|
1811
|
+
--ph-primary-frame-bg: #0b5569;
|
|
1812
|
+
--ph-secondary-button-bg: #ffffff;
|
|
1813
|
+
--ph-secondary-frame-bg: #dce4e8;
|
|
1814
|
+
--ph-secondary-button-border: #bfcbd2;
|
|
1815
|
+
--ph-secondary-button-border-hover: #8797a1;
|
|
1816
|
+
|
|
1817
|
+
--ph-border-3000: #dce4e8;
|
|
1818
|
+
--ph-chrome-inset-face-top: inset 0 1px 0 hsl(0deg 0% 100% / 0.9);
|
|
1819
|
+
--ph-chrome-inset-face-bottom: inset 0 -1px 0 hsl(0deg 0% 0% / 0.06);
|
|
1820
|
+
--ph-accent-highlight-secondary: rgb(8 145 178 / 10%);
|
|
1821
|
+
|
|
1822
|
+
--ph-data-1: #0891b2;
|
|
1823
|
+
--ph-data-2: #f59e0b;
|
|
1824
|
+
--ph-data-3: #437aff;
|
|
1825
|
+
--ph-data-4: #059669;
|
|
1826
|
+
--ph-data-5: #b26a00;
|
|
1827
|
+
--ph-data-6: #c7463a;
|
|
1828
|
+
--ph-data-7: #7c3aed;
|
|
1829
|
+
|
|
1830
|
+
--ph-product-session-replay: #b26a00;
|
|
1831
|
+
--ph-product-product-analytics: #1769aa;
|
|
1832
|
+
--ph-product-data-pipeline: #059669;
|
|
1833
|
+
--ph-product-feature-flags: #f59e0b;
|
|
1834
|
+
--ph-product-experiments: #437aff;
|
|
1835
|
+
--ph-product-data-warehouse: #7b5e3b;
|
|
1836
|
+
--ph-product-surveys: #c7463a;
|
|
1837
|
+
|
|
1838
|
+
--ph-select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='none' stroke='%235f6f7a' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 24 24'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
/* Nakama Dark — deep navy night palette with a bright cyan accent. */
|
|
1842
|
+
[data-theme="nakama-dark"] {
|
|
1843
|
+
color-scheme: dark;
|
|
1844
|
+
|
|
1845
|
+
--ph-canvas: #0f1420;
|
|
1846
|
+
--ph-surface: #161d2b;
|
|
1847
|
+
--ph-muted: #1c2434;
|
|
1848
|
+
--ph-toolbar: #232c3e;
|
|
1849
|
+
--ph-surface-inset: #121826;
|
|
1850
|
+
--ph-surface-raised: #1a2231;
|
|
1851
|
+
--ph-surface-overlay: #202a3c;
|
|
1852
|
+
--ph-border: #2b3648;
|
|
1853
|
+
--ph-border-subtle: #232d3f;
|
|
1854
|
+
--ph-border-strong: #4a5871;
|
|
1855
|
+
|
|
1856
|
+
--ph-text-primary: #e8eef4;
|
|
1857
|
+
--ph-text-secondary: #b9c4d1;
|
|
1858
|
+
--ph-text-tertiary: #8594a8;
|
|
1859
|
+
|
|
1860
|
+
--ph-accent: #22d3ee;
|
|
1861
|
+
--ph-accent-hover: #67e8f9;
|
|
1862
|
+
--ph-blue: #7da3ff;
|
|
1863
|
+
--ph-violet: #c4a7e7;
|
|
1864
|
+
--ph-purple: #b794f6;
|
|
1865
|
+
|
|
1866
|
+
--ph-success: #34d399;
|
|
1867
|
+
--ph-warning: #fbbf24;
|
|
1868
|
+
--ph-danger: #f87171;
|
|
1869
|
+
--ph-info: #60a5fa;
|
|
1870
|
+
--ph-on-accent: #062a33;
|
|
1871
|
+
--ph-on-violet: #1b1430;
|
|
1872
|
+
--ph-on-success: #052e1e;
|
|
1873
|
+
--ph-on-warning: #2b1e06;
|
|
1874
|
+
--ph-on-danger: #3b1010;
|
|
1875
|
+
--ph-on-info: #0a2a45;
|
|
1876
|
+
--ph-focus-ring: #22d3ee;
|
|
1877
|
+
--ph-selected-bg: #123a47;
|
|
1878
|
+
--ph-selected-text: #a5f3fc;
|
|
1879
|
+
--ph-selected-border: #2f6e80;
|
|
1880
|
+
|
|
1881
|
+
--ph-text-3000: #0f1420;
|
|
1882
|
+
--ph-primary-button-face: #22d3ee;
|
|
1883
|
+
--ph-primary-button-border: #3aa8bd;
|
|
1884
|
+
--ph-primary-button-border-hover: #67e8f9;
|
|
1885
|
+
--ph-primary-frame-bg: #0b6b80;
|
|
1886
|
+
--ph-secondary-button-bg: #202a3c;
|
|
1887
|
+
--ph-secondary-frame-bg: #2e3a50;
|
|
1888
|
+
--ph-secondary-button-border: #3d4b64;
|
|
1889
|
+
--ph-secondary-button-border-hover: #5a6b8a;
|
|
1890
|
+
|
|
1891
|
+
--ph-border-3000: #232d3f;
|
|
1892
|
+
--ph-chrome-inset-face-top: inset 0 1px 0 hsl(0deg 0% 100% / 0.08);
|
|
1893
|
+
--ph-chrome-inset-face-bottom: inset 0 -1px 0 hsl(0deg 0% 0% / 0.4);
|
|
1894
|
+
--ph-accent-highlight-secondary: rgb(34 211 238 / 12%);
|
|
1895
|
+
|
|
1896
|
+
--ph-data-1: #22d3ee;
|
|
1897
|
+
--ph-data-2: #fbbf24;
|
|
1898
|
+
--ph-data-3: #7da3ff;
|
|
1899
|
+
--ph-data-4: #34d399;
|
|
1900
|
+
--ph-data-5: #f59e0b;
|
|
1901
|
+
--ph-data-6: #f87171;
|
|
1902
|
+
--ph-data-7: #c4a7e7;
|
|
1903
|
+
|
|
1904
|
+
--ph-product-session-replay: #f59e0b;
|
|
1905
|
+
--ph-product-product-analytics: #60a5fa;
|
|
1906
|
+
--ph-product-data-pipeline: #34d399;
|
|
1907
|
+
--ph-product-feature-flags: #fbbf24;
|
|
1908
|
+
--ph-product-experiments: #7da3ff;
|
|
1909
|
+
--ph-product-data-warehouse: #d4a373;
|
|
1910
|
+
--ph-product-surveys: #f87171;
|
|
1911
|
+
|
|
1912
|
+
--ph-select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='none' stroke='%238594a8' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 24 24'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1767
1915
|
/* Semantic button contrast is scoped to buttons so other token consumers keep theme-native ink. */
|
|
1768
1916
|
:is(
|
|
1769
1917
|
[data-theme="hedgehog-dark"],
|
|
@@ -1780,7 +1928,8 @@
|
|
|
1780
1928
|
[data-theme="rosepine-dark"],
|
|
1781
1929
|
[data-theme="notion-dark"],
|
|
1782
1930
|
[data-theme="deepsea-dark"],
|
|
1783
|
-
[data-theme="kraken-dark"]
|
|
1931
|
+
[data-theme="kraken-dark"],
|
|
1932
|
+
[data-theme="nakama-dark"]
|
|
1784
1933
|
) {
|
|
1785
1934
|
.ph-btn-signal {
|
|
1786
1935
|
--ph-btn-face: color-mix(in oklab, var(--ph-accent) 62%, black);
|
|
@@ -1,20 +1,46 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
resolveSuiteAiLauncherPosition,
|
|
5
|
+
resolveSuiteAiNearestEdge,
|
|
6
|
+
resolveSuiteAiPanelSide,
|
|
7
|
+
} from './ai-panel';
|
|
4
8
|
|
|
5
9
|
describe('resolveSuiteAiLauncherPosition', () => {
|
|
6
|
-
it('docks to
|
|
10
|
+
it('docks to every edge and keeps the launcher inside the viewport', () => {
|
|
7
11
|
expect(
|
|
8
|
-
resolveSuiteAiLauncherPosition('left', -20, 1000, 800, 120, 40),
|
|
9
|
-
).toEqual({
|
|
10
|
-
x: 12,
|
|
11
|
-
y: 12,
|
|
12
|
-
});
|
|
12
|
+
resolveSuiteAiLauncherPosition('left', 900, -20, 1000, 800, 120, 40),
|
|
13
|
+
).toEqual({ x: 12, y: 12 });
|
|
13
14
|
expect(
|
|
14
|
-
resolveSuiteAiLauncherPosition('right', 900, 1000, 800, 120, 40),
|
|
15
|
-
).toEqual({
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
});
|
|
15
|
+
resolveSuiteAiLauncherPosition('right', 0, 900, 1000, 800, 120, 40),
|
|
16
|
+
).toEqual({ x: 868, y: 748 });
|
|
17
|
+
expect(
|
|
18
|
+
resolveSuiteAiLauncherPosition('top', -20, 900, 1000, 800, 120, 40),
|
|
19
|
+
).toEqual({ x: 12, y: 12 });
|
|
20
|
+
expect(
|
|
21
|
+
resolveSuiteAiLauncherPosition('bottom', 900, -20, 1000, 800, 120, 40),
|
|
22
|
+
).toEqual({ x: 868, y: 748 });
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('resolveSuiteAiNearestEdge', () => {
|
|
27
|
+
it('docks to the closest of all four edges', () => {
|
|
28
|
+
expect(resolveSuiteAiNearestEdge(10, 400, 120, 40, 1000, 800)).toBe('left');
|
|
29
|
+
expect(resolveSuiteAiNearestEdge(870, 400, 120, 40, 1000, 800)).toBe(
|
|
30
|
+
'right',
|
|
31
|
+
);
|
|
32
|
+
expect(resolveSuiteAiNearestEdge(440, 10, 120, 40, 1000, 800)).toBe('top');
|
|
33
|
+
expect(resolveSuiteAiNearestEdge(440, 740, 120, 40, 1000, 800)).toBe(
|
|
34
|
+
'bottom',
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('resolveSuiteAiPanelSide', () => {
|
|
40
|
+
it('opens from the docked horizontal edge or the launcher half', () => {
|
|
41
|
+
expect(resolveSuiteAiPanelSide('left', 500, 1000)).toBe('left');
|
|
42
|
+
expect(resolveSuiteAiPanelSide('right', 500, 1000)).toBe('right');
|
|
43
|
+
expect(resolveSuiteAiPanelSide('top', 100, 1000)).toBe('left');
|
|
44
|
+
expect(resolveSuiteAiPanelSide('bottom', 900, 1000)).toBe('right');
|
|
19
45
|
});
|
|
20
46
|
});
|
|
@@ -14,9 +14,7 @@ import {
|
|
|
14
14
|
} from 'react';
|
|
15
15
|
import { Check, Copy, Sparks, Xmark as X } from 'iconoir-react';
|
|
16
16
|
|
|
17
|
-
const SuiteAiBlockNoteMessage = lazy(
|
|
18
|
-
() => import('./ai-message-blocknote'),
|
|
19
|
-
);
|
|
17
|
+
const SuiteAiBlockNoteMessage = lazy(() => import('./ai-message-blocknote'));
|
|
20
18
|
|
|
21
19
|
export const SUITE_OPEN_ASK_AI_EVENT = 'shellstack:open-ask-ai';
|
|
22
20
|
|
|
@@ -45,14 +43,16 @@ export interface SuiteAiChatMessage {
|
|
|
45
43
|
created_at?: string;
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
type
|
|
46
|
+
export type SuiteAiLauncherEdge = 'top' | 'right' | 'bottom' | 'left';
|
|
47
|
+
type LauncherEdge = SuiteAiLauncherEdge;
|
|
49
48
|
type LauncherPosition = { x: number; y: number };
|
|
50
49
|
|
|
51
50
|
const LAUNCHER_EDGE_GAP = 12;
|
|
52
51
|
const LAUNCHER_STORAGE_KEY = 'suite-ask-ai-launcher-position';
|
|
53
52
|
|
|
54
53
|
export function resolveSuiteAiLauncherPosition(
|
|
55
|
-
|
|
54
|
+
edge: LauncherEdge,
|
|
55
|
+
desiredX: number,
|
|
56
56
|
desiredY: number,
|
|
57
57
|
viewportWidth: number,
|
|
58
58
|
viewportHeight: number,
|
|
@@ -67,10 +67,51 @@ export function resolveSuiteAiLauncherPosition(
|
|
|
67
67
|
LAUNCHER_EDGE_GAP,
|
|
68
68
|
viewportHeight - launcherHeight - LAUNCHER_EDGE_GAP,
|
|
69
69
|
);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
const clampX = (x: number) => Math.min(Math.max(x, LAUNCHER_EDGE_GAP), maxX);
|
|
71
|
+
const clampY = (y: number) => Math.min(Math.max(y, LAUNCHER_EDGE_GAP), maxY);
|
|
72
|
+
switch (edge) {
|
|
73
|
+
case 'left':
|
|
74
|
+
return { x: LAUNCHER_EDGE_GAP, y: clampY(desiredY) };
|
|
75
|
+
case 'right':
|
|
76
|
+
return { x: maxX, y: clampY(desiredY) };
|
|
77
|
+
case 'top':
|
|
78
|
+
return { x: clampX(desiredX), y: LAUNCHER_EDGE_GAP };
|
|
79
|
+
case 'bottom':
|
|
80
|
+
return { x: clampX(desiredX), y: maxY };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Which edge the launcher is closest to, used to dock after a drag. */
|
|
85
|
+
export function resolveSuiteAiNearestEdge(
|
|
86
|
+
x: number,
|
|
87
|
+
y: number,
|
|
88
|
+
width: number,
|
|
89
|
+
height: number,
|
|
90
|
+
viewportWidth: number,
|
|
91
|
+
viewportHeight: number,
|
|
92
|
+
): LauncherEdge {
|
|
93
|
+
const centerX = x + width / 2;
|
|
94
|
+
const centerY = y + height / 2;
|
|
95
|
+
const candidates: Array<{ edge: LauncherEdge; distance: number }> = [
|
|
96
|
+
{ edge: 'left', distance: centerX },
|
|
97
|
+
{ edge: 'right', distance: viewportWidth - centerX },
|
|
98
|
+
{ edge: 'top', distance: centerY },
|
|
99
|
+
{ edge: 'bottom', distance: viewportHeight - centerY },
|
|
100
|
+
];
|
|
101
|
+
return candidates.reduce((nearest, candidate) =>
|
|
102
|
+
candidate.distance < nearest.distance ? candidate : nearest,
|
|
103
|
+
).edge;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Which horizontal side the slide-over should open from for a docked edge. */
|
|
107
|
+
export function resolveSuiteAiPanelSide(
|
|
108
|
+
edge: LauncherEdge,
|
|
109
|
+
x: number,
|
|
110
|
+
viewportWidth: number,
|
|
111
|
+
): 'left' | 'right' {
|
|
112
|
+
if (edge === 'left') return 'left';
|
|
113
|
+
if (edge === 'right') return 'right';
|
|
114
|
+
return x < viewportWidth / 2 ? 'left' : 'right';
|
|
74
115
|
}
|
|
75
116
|
|
|
76
117
|
/**
|
|
@@ -113,13 +154,14 @@ export function SuiteAiPanel({
|
|
|
113
154
|
const [hydrating, setHydrating] = useState(false);
|
|
114
155
|
const [error, setError] = useState('');
|
|
115
156
|
const [copiedId, setCopiedId] = useState<number | null>(null);
|
|
116
|
-
const [launcherSide, setLauncherSide] = useState<
|
|
157
|
+
const [launcherSide, setLauncherSide] = useState<LauncherEdge>('right');
|
|
158
|
+
const [panelSide, setPanelSide] = useState<'left' | 'right'>('right');
|
|
117
159
|
const [launcherPosition, setLauncherPosition] =
|
|
118
160
|
useState<LauncherPosition | null>(null);
|
|
119
161
|
const [draggingLauncher, setDraggingLauncher] = useState(false);
|
|
120
162
|
const scrollerRef = useRef<HTMLDivElement>(null);
|
|
121
163
|
const launcherRef = useRef<HTMLButtonElement>(null);
|
|
122
|
-
const launcherSideRef = useRef<
|
|
164
|
+
const launcherSideRef = useRef<LauncherEdge>('right');
|
|
123
165
|
const launcherPositionRef = useRef<LauncherPosition | null>(null);
|
|
124
166
|
const launcherDragRef = useRef<{
|
|
125
167
|
pointerId: number;
|
|
@@ -135,17 +177,25 @@ export function SuiteAiPanel({
|
|
|
135
177
|
const launcher = launcherRef.current;
|
|
136
178
|
if (!launcher) return;
|
|
137
179
|
|
|
138
|
-
let side:
|
|
180
|
+
let side: LauncherEdge = 'right';
|
|
181
|
+
let desiredX = LAUNCHER_EDGE_GAP;
|
|
139
182
|
let desiredY = window.innerHeight - launcher.offsetHeight - 20;
|
|
140
183
|
try {
|
|
141
184
|
const stored = JSON.parse(
|
|
142
185
|
localStorage.getItem(LAUNCHER_STORAGE_KEY) || 'null',
|
|
143
186
|
) as {
|
|
144
|
-
side?:
|
|
187
|
+
side?: LauncherEdge;
|
|
188
|
+
x?: number;
|
|
145
189
|
y?: number;
|
|
146
190
|
} | null;
|
|
147
|
-
if (
|
|
191
|
+
if (
|
|
192
|
+
stored?.side === 'top' ||
|
|
193
|
+
stored?.side === 'right' ||
|
|
194
|
+
stored?.side === 'bottom' ||
|
|
195
|
+
stored?.side === 'left'
|
|
196
|
+
)
|
|
148
197
|
side = stored.side;
|
|
198
|
+
if (typeof stored?.x === 'number') desiredX = stored.x;
|
|
149
199
|
if (typeof stored?.y === 'number') desiredY = stored.y;
|
|
150
200
|
} catch {
|
|
151
201
|
// Ignore invalid saved launcher positions.
|
|
@@ -159,6 +209,7 @@ export function SuiteAiPanel({
|
|
|
159
209
|
if (!element) return;
|
|
160
210
|
const next = resolveSuiteAiLauncherPosition(
|
|
161
211
|
launcherSideRef.current,
|
|
212
|
+
launcherPositionRef.current?.x ?? desiredX,
|
|
162
213
|
launcherPositionRef.current?.y ?? desiredY,
|
|
163
214
|
window.innerWidth,
|
|
164
215
|
window.innerHeight,
|
|
@@ -167,6 +218,13 @@ export function SuiteAiPanel({
|
|
|
167
218
|
);
|
|
168
219
|
launcherPositionRef.current = next;
|
|
169
220
|
setLauncherPosition(next);
|
|
221
|
+
setPanelSide(
|
|
222
|
+
resolveSuiteAiPanelSide(
|
|
223
|
+
launcherSideRef.current,
|
|
224
|
+
next.x,
|
|
225
|
+
window.innerWidth,
|
|
226
|
+
),
|
|
227
|
+
);
|
|
170
228
|
};
|
|
171
229
|
|
|
172
230
|
placeLauncher();
|
|
@@ -280,35 +338,41 @@ export function SuiteAiPanel({
|
|
|
280
338
|
}
|
|
281
339
|
|
|
282
340
|
function saveLauncherPosition(
|
|
283
|
-
|
|
341
|
+
edge: LauncherEdge,
|
|
284
342
|
position: LauncherPosition,
|
|
285
343
|
) {
|
|
286
344
|
try {
|
|
287
345
|
localStorage.setItem(
|
|
288
346
|
LAUNCHER_STORAGE_KEY,
|
|
289
|
-
JSON.stringify({ side, y: position.y }),
|
|
347
|
+
JSON.stringify({ side: edge, x: position.x, y: position.y }),
|
|
290
348
|
);
|
|
291
349
|
} catch {
|
|
292
350
|
// Storage may be unavailable; dragging still works for this session.
|
|
293
351
|
}
|
|
294
352
|
}
|
|
295
353
|
|
|
296
|
-
function dockLauncher(
|
|
354
|
+
function dockLauncher(
|
|
355
|
+
edge: LauncherEdge,
|
|
356
|
+
desiredX: number,
|
|
357
|
+
desiredY: number,
|
|
358
|
+
) {
|
|
297
359
|
const launcher = launcherRef.current;
|
|
298
360
|
if (!launcher) return;
|
|
299
361
|
const next = resolveSuiteAiLauncherPosition(
|
|
300
|
-
|
|
362
|
+
edge,
|
|
363
|
+
desiredX,
|
|
301
364
|
desiredY,
|
|
302
365
|
window.innerWidth,
|
|
303
366
|
window.innerHeight,
|
|
304
367
|
launcher.offsetWidth,
|
|
305
368
|
launcher.offsetHeight,
|
|
306
369
|
);
|
|
307
|
-
launcherSideRef.current =
|
|
370
|
+
launcherSideRef.current = edge;
|
|
308
371
|
launcherPositionRef.current = next;
|
|
309
|
-
setLauncherSide(
|
|
372
|
+
setLauncherSide(edge);
|
|
310
373
|
setLauncherPosition(next);
|
|
311
|
-
|
|
374
|
+
setPanelSide(resolveSuiteAiPanelSide(edge, next.x, window.innerWidth));
|
|
375
|
+
saveLauncherPosition(edge, next);
|
|
312
376
|
}
|
|
313
377
|
|
|
314
378
|
function startLauncherDrag(event: ReactPointerEvent<HTMLButtonElement>) {
|
|
@@ -367,29 +431,34 @@ export function SuiteAiPanel({
|
|
|
367
431
|
setDraggingLauncher(false);
|
|
368
432
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
369
433
|
const currentX = launcherPositionRef.current?.x ?? rect.left;
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
434
|
+
const currentY = launcherPositionRef.current?.y ?? rect.top;
|
|
435
|
+
const edge = resolveSuiteAiNearestEdge(
|
|
436
|
+
currentX,
|
|
437
|
+
currentY,
|
|
438
|
+
rect.width,
|
|
439
|
+
rect.height,
|
|
440
|
+
window.innerWidth,
|
|
441
|
+
window.innerHeight,
|
|
442
|
+
);
|
|
443
|
+
dockLauncher(edge, currentX, currentY);
|
|
373
444
|
}
|
|
374
445
|
|
|
375
446
|
function moveLauncherWithKeyboard(
|
|
376
447
|
event: ReactKeyboardEvent<HTMLButtonElement>,
|
|
377
448
|
) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
449
|
+
const edgeByKey: Record<string, LauncherEdge> = {
|
|
450
|
+
ArrowLeft: 'left',
|
|
451
|
+
ArrowRight: 'right',
|
|
452
|
+
ArrowUp: 'top',
|
|
453
|
+
ArrowDown: 'bottom',
|
|
454
|
+
};
|
|
455
|
+
const edge = edgeByKey[event.key];
|
|
456
|
+
if (!edge) return;
|
|
382
457
|
event.preventDefault();
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
else if (event.key === 'ArrowRight') dockLauncher('right', currentY);
|
|
388
|
-
else
|
|
389
|
-
dockLauncher(
|
|
390
|
-
launcherSideRef.current,
|
|
391
|
-
currentY + (event.key === 'ArrowUp' ? -24 : 24),
|
|
392
|
-
);
|
|
458
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
459
|
+
const currentX = launcherPositionRef.current?.x ?? rect.left;
|
|
460
|
+
const currentY = launcherPositionRef.current?.y ?? rect.top;
|
|
461
|
+
dockLauncher(edge, currentX, currentY);
|
|
393
462
|
}
|
|
394
463
|
|
|
395
464
|
const Icon = BrandIcon ?? Sparks;
|
|
@@ -442,7 +511,7 @@ export function SuiteAiPanel({
|
|
|
442
511
|
aria-label="Close Ask AI"
|
|
443
512
|
/>
|
|
444
513
|
<aside
|
|
445
|
-
className={`absolute inset-y-0 flex w-full max-w-md flex-col bg-ph-surface shadow-2xl ${
|
|
514
|
+
className={`absolute inset-y-0 flex w-full max-w-md flex-col bg-ph-surface shadow-2xl ${panelSide === 'left' ? 'left-0 border-r border-ph-border' : 'right-0 border-l border-ph-border'}`}
|
|
446
515
|
data-test="suite-ask-ai-panel"
|
|
447
516
|
>
|
|
448
517
|
<div className="flex items-center justify-between border-b border-ph-border px-4 py-3">
|
|
@@ -12,7 +12,8 @@ export type SuiteAccentSlug =
|
|
|
12
12
|
| 'tides'
|
|
13
13
|
| 'kraken'
|
|
14
14
|
| 'shellstack'
|
|
15
|
-
| 'crew'
|
|
15
|
+
| 'crew'
|
|
16
|
+
| 'nakama';
|
|
16
17
|
|
|
17
18
|
export interface SuiteAppAccent {
|
|
18
19
|
/** Display name (default aria label for app marks). */
|
|
@@ -63,6 +64,13 @@ export const APP_ACCENTS: Record<SuiteAccentSlug, SuiteAppAccent> = {
|
|
|
63
64
|
onTile: '#ffffff',
|
|
64
65
|
onTileAccent: '#a5f3fc',
|
|
65
66
|
},
|
|
67
|
+
nakama: {
|
|
68
|
+
label: 'Nakama',
|
|
69
|
+
accent: '#0891b2',
|
|
70
|
+
tile: '#155e75',
|
|
71
|
+
onTile: '#ffffff',
|
|
72
|
+
onTileAccent: '#a5f3fc',
|
|
73
|
+
},
|
|
66
74
|
};
|
|
67
75
|
|
|
68
76
|
/** Which glyph represents each app. */
|
|
@@ -72,4 +80,5 @@ export const APP_GLYPHS: Record<SuiteAccentSlug, SuiteIconName> = {
|
|
|
72
80
|
kraken: 'squid-doc',
|
|
73
81
|
shellstack: 'stack-hex',
|
|
74
82
|
crew: 'crew-bot',
|
|
83
|
+
nakama: 'crew-bot',
|
|
75
84
|
};
|
package/src/suite/lib/apps.ts
CHANGED
|
@@ -9,7 +9,8 @@ export type SuiteAppSlug =
|
|
|
9
9
|
| 'shellstack'
|
|
10
10
|
| 'tides'
|
|
11
11
|
| 'turtletime'
|
|
12
|
-
| 'kraken'
|
|
12
|
+
| 'kraken'
|
|
13
|
+
| 'nakama';
|
|
13
14
|
|
|
14
15
|
export interface SuiteAppDefinition {
|
|
15
16
|
slug: SuiteAppSlug;
|
|
@@ -62,6 +63,15 @@ export const SUITE_APPS: readonly SuiteAppDefinition[] = [
|
|
|
62
63
|
baseUrlEnv: 'NEXT_PUBLIC_KRAKEN_URL',
|
|
63
64
|
baseUrlFallback: 'http://localhost:3003',
|
|
64
65
|
},
|
|
66
|
+
{
|
|
67
|
+
slug: 'nakama',
|
|
68
|
+
name: 'Nakama',
|
|
69
|
+
description: 'AI teammates',
|
|
70
|
+
icon: '/nakama-icon.svg',
|
|
71
|
+
landing: '/today',
|
|
72
|
+
baseUrlEnv: 'NEXT_PUBLIC_NAKAMA_URL',
|
|
73
|
+
baseUrlFallback: 'http://localhost:3004',
|
|
74
|
+
},
|
|
65
75
|
] as const;
|
|
66
76
|
|
|
67
77
|
export const SUITE_APP_MAP: Readonly<Record<SuiteAppSlug, SuiteAppDefinition>> =
|
|
@@ -82,6 +92,7 @@ const SOURCE_APP_SLUG: Record<string, SuiteAppSlug> = {
|
|
|
82
92
|
turtletime: 'turtletime',
|
|
83
93
|
tides: 'tides',
|
|
84
94
|
kraken: 'kraken',
|
|
95
|
+
nakama: 'nakama',
|
|
85
96
|
portal: 'shellstack',
|
|
86
97
|
shellstack: 'shellstack',
|
|
87
98
|
};
|