@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.
Files changed (40) hide show
  1. package/README.md +5 -0
  2. package/components.json +19 -0
  3. package/package.json +56 -0
  4. package/src/components/account-drawer.tsx +133 -0
  5. package/src/components/on-screen-controls.tsx +156 -0
  6. package/src/components/prop-catalog.tsx +583 -0
  7. package/src/components/scene-settings.tsx +202 -0
  8. package/src/components/theme-provider.tsx +30 -0
  9. package/src/components/theme-toggle.tsx +73 -0
  10. package/src/components/ui/badge.tsx +49 -0
  11. package/src/components/ui/button.tsx +60 -0
  12. package/src/components/ui/card.tsx +18 -0
  13. package/src/components/ui/dialog.tsx +129 -0
  14. package/src/components/ui/drawer.tsx +209 -0
  15. package/src/components/ui/dropdown-menu.tsx +258 -0
  16. package/src/components/ui/empty.tsx +94 -0
  17. package/src/components/ui/field.tsx +224 -0
  18. package/src/components/ui/input.tsx +20 -0
  19. package/src/components/ui/label.tsx +20 -0
  20. package/src/components/ui/radio-group.tsx +38 -0
  21. package/src/components/ui/separator.tsx +21 -0
  22. package/src/components/ui/skeleton.tsx +13 -0
  23. package/src/components/ui/spinner.tsx +16 -0
  24. package/src/components/ui/switch.tsx +32 -0
  25. package/src/components/ui/tabs.tsx +75 -0
  26. package/src/components/ui/toggle-group.tsx +87 -0
  27. package/src/components/ui/toggle.tsx +45 -0
  28. package/src/components/ui/tooltip.tsx +56 -0
  29. package/src/components/version-dialog.tsx +366 -0
  30. package/src/components/workspace-brand.tsx +18 -0
  31. package/src/components/workspace-logo.tsx +17 -0
  32. package/src/index.tsx +55 -0
  33. package/src/lib/utils.ts +6 -0
  34. package/src/lib/version-notes.ts +30 -0
  35. package/src/styles/auth-shell.css +67 -0
  36. package/src/styles/conventional-workspace.css +2846 -0
  37. package/src/styles/globals.css +93 -0
  38. package/src/styles/theme.css +94 -0
  39. package/src/styles/workspace-shell.css +145 -0
  40. package/tsconfig.json +10 -0
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Shared UI
2
+
3
+ Reusable Base UI/shadcn primitives and React controls for the HQ shell, scene
4
+ controls, room authoring, themes, drawers, loading states, and overlays. Scene
5
+ branding and Three.js runtime behavior stay in their owning packages.
@@ -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
+ }