@adea-ai/ui 0.10.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/components.json +19 -0
- package/package.json +56 -0
- package/src/components/account-drawer.tsx +133 -0
- package/src/components/on-screen-controls.tsx +156 -0
- package/src/components/prop-catalog.tsx +583 -0
- package/src/components/scene-settings.tsx +202 -0
- package/src/components/theme-provider.tsx +30 -0
- package/src/components/theme-toggle.tsx +73 -0
- package/src/components/ui/badge.tsx +49 -0
- package/src/components/ui/button.tsx +60 -0
- package/src/components/ui/card.tsx +18 -0
- package/src/components/ui/dialog.tsx +129 -0
- package/src/components/ui/drawer.tsx +209 -0
- package/src/components/ui/dropdown-menu.tsx +258 -0
- package/src/components/ui/empty.tsx +94 -0
- package/src/components/ui/field.tsx +224 -0
- package/src/components/ui/input.tsx +20 -0
- package/src/components/ui/label.tsx +20 -0
- package/src/components/ui/radio-group.tsx +38 -0
- package/src/components/ui/separator.tsx +21 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +32 -0
- package/src/components/ui/tabs.tsx +75 -0
- package/src/components/ui/toggle-group.tsx +87 -0
- package/src/components/ui/toggle.tsx +45 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/version-dialog.tsx +366 -0
- package/src/components/workspace-brand.tsx +18 -0
- package/src/components/workspace-logo.tsx +17 -0
- package/src/index.tsx +55 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/version-notes.ts +30 -0
- package/src/styles/auth-shell.css +67 -0
- package/src/styles/conventional-workspace.css +2846 -0
- package/src/styles/globals.css +93 -0
- package/src/styles/theme.css +94 -0
- package/src/styles/workspace-shell.css +145 -0
- package/tsconfig.json +10 -0
package/README.md
ADDED
package/components.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "base-nova",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/styles/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true
|
|
11
|
+
},
|
|
12
|
+
"iconLibrary": "lucide",
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "#components",
|
|
15
|
+
"utils": "#lib/utils",
|
|
16
|
+
"lib": "#lib",
|
|
17
|
+
"ui": "#components/ui"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adea-ai/ui",
|
|
3
|
+
"version": "0.10.8",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"development": "./src/index.tsx",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./components/*": {
|
|
14
|
+
"types": "./dist/components/*.d.ts",
|
|
15
|
+
"development": "./src/components/*",
|
|
16
|
+
"import": "./dist/components/*.js"
|
|
17
|
+
},
|
|
18
|
+
"./lib/*": {
|
|
19
|
+
"types": "./dist/lib/*.d.ts",
|
|
20
|
+
"development": "./src/lib/*",
|
|
21
|
+
"import": "./dist/lib/*.js"
|
|
22
|
+
},
|
|
23
|
+
"./globals.css": "./src/styles/globals.css",
|
|
24
|
+
"./auth-shell.css": "./src/styles/auth-shell.css",
|
|
25
|
+
"./conventional-workspace.css": "./src/styles/conventional-workspace.css",
|
|
26
|
+
"./theme.css": "./src/styles/theme.css",
|
|
27
|
+
"./workspace-shell.css": "./src/styles/workspace-shell.css"
|
|
28
|
+
},
|
|
29
|
+
"imports": {
|
|
30
|
+
"#components/*": "./src/components/*.tsx",
|
|
31
|
+
"#lib/*": "./src/lib/*.ts"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"lint": "eslint .",
|
|
35
|
+
"build": "tsc -p tsconfig.json",
|
|
36
|
+
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
|
|
37
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@base-ui/react": "^1.7.0",
|
|
41
|
+
"class-variance-authority": "^0.7.1",
|
|
42
|
+
"clsx": "^2.1.1",
|
|
43
|
+
"lucide-react": "^1.33.0",
|
|
44
|
+
"next-themes": "^0.4.6",
|
|
45
|
+
"react": "19.2.8",
|
|
46
|
+
"react-dom": "19.2.8",
|
|
47
|
+
"tailwind-merge": "^3.6.0",
|
|
48
|
+
"three": "^0.185.1",
|
|
49
|
+
"tw-animate-css": "^1.4.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/react": "^19.2.18",
|
|
53
|
+
"@types/react-dom": "^19.2.4",
|
|
54
|
+
"@types/three": "^0.185.0"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import { useEffect, useState, type ReactNode } from "react";
|
|
5
|
+
import { UserRound } from "lucide-react";
|
|
6
|
+
import { Button } from "#components/ui/button";
|
|
7
|
+
import {
|
|
8
|
+
Drawer,
|
|
9
|
+
DrawerClose,
|
|
10
|
+
DrawerContent,
|
|
11
|
+
DrawerDescription,
|
|
12
|
+
DrawerHeader,
|
|
13
|
+
DrawerTitle,
|
|
14
|
+
DrawerTrigger,
|
|
15
|
+
} from "#components/ui/drawer";
|
|
16
|
+
import { ThemeToggle } from "./theme-toggle";
|
|
17
|
+
|
|
18
|
+
export type AccountDrawerProps = {
|
|
19
|
+
accountLabel?: string;
|
|
20
|
+
authenticated?: boolean;
|
|
21
|
+
busy?: boolean;
|
|
22
|
+
musicControl?: ReactNode;
|
|
23
|
+
triggerTargetId?: string;
|
|
24
|
+
onSignIn?: () => void;
|
|
25
|
+
onSignOut?: () => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function AccountDrawer({
|
|
29
|
+
accountLabel = "Sign in",
|
|
30
|
+
authenticated = false,
|
|
31
|
+
busy = false,
|
|
32
|
+
musicControl,
|
|
33
|
+
triggerTargetId,
|
|
34
|
+
onSignIn,
|
|
35
|
+
onSignOut,
|
|
36
|
+
}: AccountDrawerProps) {
|
|
37
|
+
const [triggerTarget, setTriggerTarget] = useState<HTMLElement | null>(null);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
setTriggerTarget(triggerTargetId ? document.getElementById(triggerTargetId) : null);
|
|
41
|
+
}, [triggerTargetId]);
|
|
42
|
+
|
|
43
|
+
const trigger = (
|
|
44
|
+
<DrawerTrigger
|
|
45
|
+
render={
|
|
46
|
+
<Button
|
|
47
|
+
variant="outline"
|
|
48
|
+
size="sm"
|
|
49
|
+
className="workspace-account-trigger"
|
|
50
|
+
aria-label={`Open user menu for ${accountLabel}`}
|
|
51
|
+
aria-haspopup="dialog"
|
|
52
|
+
/>
|
|
53
|
+
}
|
|
54
|
+
>
|
|
55
|
+
<UserRound aria-hidden="true" />
|
|
56
|
+
<span>{accountLabel}</span>
|
|
57
|
+
</DrawerTrigger>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Drawer swipeDirection="right">
|
|
62
|
+
{triggerTarget ? (
|
|
63
|
+
createPortal(trigger, triggerTarget)
|
|
64
|
+
) : (
|
|
65
|
+
<div className="fixed right-4 top-4 z-40">{trigger}</div>
|
|
66
|
+
)}
|
|
67
|
+
<DrawerContent className="w-[min(24rem,90vw)]">
|
|
68
|
+
<DrawerHeader className="border-b px-5 pb-4 pt-5 text-left">
|
|
69
|
+
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-4">
|
|
70
|
+
<div className="flex min-w-0 items-center gap-3">
|
|
71
|
+
<span className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-primary text-primary-foreground">
|
|
72
|
+
<UserRound className="size-5" aria-hidden="true" />
|
|
73
|
+
</span>
|
|
74
|
+
<div className="min-w-0">
|
|
75
|
+
<DrawerTitle className="truncate">{accountLabel}</DrawerTitle>
|
|
76
|
+
<DrawerDescription>
|
|
77
|
+
{authenticated
|
|
78
|
+
? "Signed in · workspace saved"
|
|
79
|
+
: "Guest workspace · sign in anytime"}
|
|
80
|
+
</DrawerDescription>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
<ThemeToggle className="shrink-0" />
|
|
84
|
+
</div>
|
|
85
|
+
</DrawerHeader>
|
|
86
|
+
<div className="flex min-h-0 flex-1 flex-col gap-6 overflow-y-auto p-5">
|
|
87
|
+
{musicControl ? (
|
|
88
|
+
<section
|
|
89
|
+
className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-4 border-t pt-5"
|
|
90
|
+
aria-labelledby="account-music-title"
|
|
91
|
+
>
|
|
92
|
+
<div className="min-w-0">
|
|
93
|
+
<h2 id="account-music-title" className="text-sm font-semibold">
|
|
94
|
+
Music
|
|
95
|
+
</h2>
|
|
96
|
+
<p className="text-sm text-muted-foreground">Control the workspace soundtrack.</p>
|
|
97
|
+
</div>
|
|
98
|
+
<div className="flex shrink-0 items-center justify-end">{musicControl}</div>
|
|
99
|
+
</section>
|
|
100
|
+
) : null}
|
|
101
|
+
<section
|
|
102
|
+
className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-4 border-t pt-5"
|
|
103
|
+
aria-labelledby="account-session-title"
|
|
104
|
+
>
|
|
105
|
+
<div className="min-w-0">
|
|
106
|
+
<h2 id="account-session-title" className="text-sm font-semibold">
|
|
107
|
+
Account
|
|
108
|
+
</h2>
|
|
109
|
+
<p className="text-sm text-muted-foreground">
|
|
110
|
+
{authenticated
|
|
111
|
+
? "Sign out on this device without removing your saved workspace."
|
|
112
|
+
: "Sign in or create an account to keep this workspace across devices."}
|
|
113
|
+
</p>
|
|
114
|
+
</div>
|
|
115
|
+
<DrawerClose
|
|
116
|
+
render={
|
|
117
|
+
<Button
|
|
118
|
+
type="button"
|
|
119
|
+
className="shrink-0"
|
|
120
|
+
variant={authenticated ? "outline" : "default"}
|
|
121
|
+
disabled={busy}
|
|
122
|
+
onClick={authenticated ? onSignOut : onSignIn}
|
|
123
|
+
/>
|
|
124
|
+
}
|
|
125
|
+
>
|
|
126
|
+
{authenticated ? "Sign out" : "Sign in"}
|
|
127
|
+
</DrawerClose>
|
|
128
|
+
</section>
|
|
129
|
+
</div>
|
|
130
|
+
</DrawerContent>
|
|
131
|
+
</Drawer>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
4
|
+
import type { PointerEvent, ReactNode } from "react";
|
|
5
|
+
import { ZoomIn, ZoomOut } from "lucide-react";
|
|
6
|
+
import { Button } from "#components/ui/button";
|
|
7
|
+
import { cn } from "#lib/utils";
|
|
8
|
+
type ControlButtonProps = {
|
|
9
|
+
code: string;
|
|
10
|
+
label: string;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
className?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function sendKeyEvent(type: "keydown" | "keyup", code: string): void {
|
|
16
|
+
window.dispatchEvent(
|
|
17
|
+
new KeyboardEvent(type, { bubbles: true, cancelable: true, code, key: code })
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ControlButton({ code, label, children, className }: ControlButtonProps) {
|
|
22
|
+
const pressed = useRef(false);
|
|
23
|
+
const pressedPointer = useRef<number | null>(null);
|
|
24
|
+
|
|
25
|
+
const release = useCallback(() => {
|
|
26
|
+
if (!pressed.current) return;
|
|
27
|
+
pressed.current = false;
|
|
28
|
+
pressedPointer.current = null;
|
|
29
|
+
sendKeyEvent("keyup", code);
|
|
30
|
+
}, [code]);
|
|
31
|
+
|
|
32
|
+
// Self-heal: release the key if the pointerup lands elsewhere (e.g. the
|
|
33
|
+
// scene DOM changes mid-teleport) or the window loses focus. PointerId
|
|
34
|
+
// matching preserves multi-touch.
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const onWindowPointerUp = (event: globalThis.PointerEvent) => {
|
|
37
|
+
if (pressedPointer.current === event.pointerId) release();
|
|
38
|
+
};
|
|
39
|
+
const onBlur = () => release();
|
|
40
|
+
window.addEventListener("pointerup", onWindowPointerUp);
|
|
41
|
+
window.addEventListener("blur", onBlur);
|
|
42
|
+
return () => {
|
|
43
|
+
window.removeEventListener("pointerup", onWindowPointerUp);
|
|
44
|
+
window.removeEventListener("blur", onBlur);
|
|
45
|
+
};
|
|
46
|
+
}, [release]);
|
|
47
|
+
|
|
48
|
+
const press = useCallback(
|
|
49
|
+
(event: PointerEvent<HTMLButtonElement>) => {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
if (pressed.current) return;
|
|
52
|
+
pressed.current = true;
|
|
53
|
+
pressedPointer.current = event.pointerId;
|
|
54
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
55
|
+
sendKeyEvent("keydown", code);
|
|
56
|
+
},
|
|
57
|
+
[code]
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<button
|
|
62
|
+
type="button"
|
|
63
|
+
aria-label={label}
|
|
64
|
+
className={cn(
|
|
65
|
+
"flex size-14 touch-none select-none items-center justify-center rounded-2xl border border-white/25 bg-slate-950/65 p-2 text-white shadow-lg backdrop-blur-sm transition active:scale-95 [-webkit-touch-callout:none] [-webkit-user-select:none]",
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
onContextMenu={(event) => event.preventDefault()}
|
|
69
|
+
onPointerCancel={release}
|
|
70
|
+
onPointerDown={press}
|
|
71
|
+
onPointerLeave={release}
|
|
72
|
+
onPointerUp={release}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
</button>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type OnScreenControlsProps = {
|
|
80
|
+
onZoomIn?: () => void;
|
|
81
|
+
onZoomOut?: () => void;
|
|
82
|
+
showMovementControls?: boolean;
|
|
83
|
+
showJumpControl?: boolean;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export function OnScreenControls({
|
|
87
|
+
onZoomIn,
|
|
88
|
+
onZoomOut,
|
|
89
|
+
showMovementControls = true,
|
|
90
|
+
showJumpControl = true,
|
|
91
|
+
}: OnScreenControlsProps = {}) {
|
|
92
|
+
const showZoomControls = Boolean(onZoomIn && onZoomOut);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div
|
|
96
|
+
data-agent-hq-on-screen-controls
|
|
97
|
+
className="pointer-events-none fixed inset-x-4 bottom-4 z-30 flex select-none items-end justify-between gap-4 pb-[env(safe-area-inset-bottom)] sm:inset-x-6 sm:bottom-6 [-webkit-touch-callout:none] [-webkit-user-select:none]"
|
|
98
|
+
>
|
|
99
|
+
{showMovementControls ? (
|
|
100
|
+
<div className="pointer-events-auto grid grid-cols-3 gap-1.5 rounded-3xl bg-slate-950/20 p-2 backdrop-blur-[2px]">
|
|
101
|
+
<span />
|
|
102
|
+
<ControlButton code="KeyW" label="Move forward" className="size-12 rounded-xl text-xl">
|
|
103
|
+
▲
|
|
104
|
+
</ControlButton>
|
|
105
|
+
<span />
|
|
106
|
+
<ControlButton code="KeyA" label="Move left" className="size-12 rounded-xl text-xl">
|
|
107
|
+
◀
|
|
108
|
+
</ControlButton>
|
|
109
|
+
<ControlButton code="KeyS" label="Move backward" className="size-12 rounded-xl text-xl">
|
|
110
|
+
▼
|
|
111
|
+
</ControlButton>
|
|
112
|
+
<ControlButton code="KeyD" label="Move right" className="size-12 rounded-xl text-xl">
|
|
113
|
+
▶
|
|
114
|
+
</ControlButton>
|
|
115
|
+
</div>
|
|
116
|
+
) : null}
|
|
117
|
+
<div className="pointer-events-auto ml-auto flex flex-col items-end gap-2">
|
|
118
|
+
{showZoomControls ? (
|
|
119
|
+
<div
|
|
120
|
+
className="flex items-center gap-1.5 rounded-2xl bg-slate-950/20 p-1.5 backdrop-blur-[2px]"
|
|
121
|
+
role="group"
|
|
122
|
+
aria-label="Camera zoom"
|
|
123
|
+
>
|
|
124
|
+
<Button
|
|
125
|
+
type="button"
|
|
126
|
+
variant="secondary"
|
|
127
|
+
size="icon-lg"
|
|
128
|
+
aria-label="Zoom out"
|
|
129
|
+
title="Zoom out"
|
|
130
|
+
className="border border-white/25 bg-slate-950/65 text-white shadow-lg backdrop-blur-sm hover:bg-slate-900/80"
|
|
131
|
+
onClick={onZoomOut}
|
|
132
|
+
>
|
|
133
|
+
<ZoomOut aria-hidden="true" />
|
|
134
|
+
</Button>
|
|
135
|
+
<Button
|
|
136
|
+
type="button"
|
|
137
|
+
variant="secondary"
|
|
138
|
+
size="icon-lg"
|
|
139
|
+
aria-label="Zoom in"
|
|
140
|
+
title="Zoom in"
|
|
141
|
+
className="border border-white/25 bg-slate-950/65 text-white shadow-lg backdrop-blur-sm hover:bg-slate-900/80"
|
|
142
|
+
onClick={onZoomIn}
|
|
143
|
+
>
|
|
144
|
+
<ZoomIn aria-hidden="true" />
|
|
145
|
+
</Button>
|
|
146
|
+
</div>
|
|
147
|
+
) : null}
|
|
148
|
+
{showJumpControl ? (
|
|
149
|
+
<ControlButton code="Space" label="Jump" className="text-xs font-semibold">
|
|
150
|
+
JUMP
|
|
151
|
+
</ControlButton>
|
|
152
|
+
) : null}
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
}
|