@praxisjs/devtools 0.1.0
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/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/assets/logo.svg +9 -0
- package/assets/uno.generated.css +205 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.js +1051 -0
- package/package.json +35 -0
- package/src/core/event-bus.ts +19 -0
- package/src/core/registry.ts +219 -0
- package/src/core/types.ts +45 -0
- package/src/decorators/debug.ts +161 -0
- package/src/decorators/index.ts +3 -0
- package/src/decorators/trace.ts +64 -0
- package/src/icons/ellipsis-vertical.tsx +20 -0
- package/src/icons/index.ts +6 -0
- package/src/icons/panel-bottom.tsx +19 -0
- package/src/icons/panel-left.tsx +19 -0
- package/src/icons/panel-right.tsx +19 -0
- package/src/icons/panel-top.tsx +19 -0
- package/src/icons/x.tsx +19 -0
- package/src/index.ts +20 -0
- package/src/plugins/components/components/component-detail.tsx +70 -0
- package/src/plugins/components/components/component-row.tsx +42 -0
- package/src/plugins/components/components/detail-row.tsx +22 -0
- package/src/plugins/components/components/detail-section.tsx +18 -0
- package/src/plugins/components/components/status-dot.tsx +14 -0
- package/src/plugins/components/components-tab.tsx +85 -0
- package/src/plugins/components/index.ts +9 -0
- package/src/plugins/signals/components/signal-detail.tsx +35 -0
- package/src/plugins/signals/components/signal-row.tsx +37 -0
- package/src/plugins/signals/index.ts +9 -0
- package/src/plugins/signals/signals-tab.tsx +99 -0
- package/src/plugins/timeline/components/badge.tsx +14 -0
- package/src/plugins/timeline/components/timeline-row.tsx +55 -0
- package/src/plugins/timeline/constants.ts +24 -0
- package/src/plugins/timeline/index.ts +9 -0
- package/src/plugins/timeline/timeline-tab.tsx +101 -0
- package/src/plugins/types.ts +10 -0
- package/src/ui/dev-tools.tsx +121 -0
- package/src/ui/panel.tsx +225 -0
- package/src/ui/shared/empty-state.tsx +12 -0
- package/src/ui/shared/icon-button.tsx +30 -0
- package/src/ui/shared/panel-section.tsx +18 -0
- package/src/ui/shared/search-input.tsx +18 -0
- package/src/ui/shared/side-panel.tsx +18 -0
- package/src/utils/format-time.ts +7 -0
- package/src/utils/format-value.ts +13 -0
- package/src/vite-env.d.ts +6 -0
- package/tsconfig.json +21 -0
- package/uno.config.ts +57 -0
- package/vite.config.ts +42 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FunctionComponent } from "@praxisjs/shared";
|
|
2
|
+
|
|
3
|
+
import type { Registry } from "@core/registry";
|
|
4
|
+
|
|
5
|
+
export interface DevtoolsPlugin {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
setup?: (registry: Registry) => void;
|
|
9
|
+
component: FunctionComponent<{ registry: Registry }>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import logo from "@assets/logo.svg";
|
|
2
|
+
import unoStyles from "@assets/uno.generated.css?inline";
|
|
3
|
+
import { Registry } from "@core/registry";
|
|
4
|
+
import { ComponentsPlugin } from "@plugins/components";
|
|
5
|
+
import { SignalsPlugin } from "@plugins/signals";
|
|
6
|
+
import { TimelinePlugin } from "@plugins/timeline";
|
|
7
|
+
import unoReset from "@unocss/reset/tailwind-v4.css?inline";
|
|
8
|
+
|
|
9
|
+
import { signal } from "@praxisjs/core";
|
|
10
|
+
import { render } from "@praxisjs/runtime";
|
|
11
|
+
|
|
12
|
+
import { Panel } from "./panel";
|
|
13
|
+
|
|
14
|
+
import type { DevtoolsPlugin } from "@plugins/types";
|
|
15
|
+
|
|
16
|
+
const DEFAULT_PLUGINS: DevtoolsPlugin[] = [
|
|
17
|
+
SignalsPlugin,
|
|
18
|
+
ComponentsPlugin,
|
|
19
|
+
TimelinePlugin,
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export interface DevToolsOptions {
|
|
23
|
+
plugins?: DevtoolsPlugin[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function DevToolsApp({
|
|
27
|
+
plugins,
|
|
28
|
+
registry,
|
|
29
|
+
}: {
|
|
30
|
+
plugins: DevtoolsPlugin[];
|
|
31
|
+
registry: Registry;
|
|
32
|
+
}) {
|
|
33
|
+
const open = signal(false);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div>
|
|
37
|
+
{() =>
|
|
38
|
+
open() ? (
|
|
39
|
+
<Panel
|
|
40
|
+
plugins={plugins}
|
|
41
|
+
registry={registry}
|
|
42
|
+
onClose={() => {
|
|
43
|
+
open.set(false);
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
) : (
|
|
47
|
+
<button
|
|
48
|
+
onClick={() => {
|
|
49
|
+
open.set(true);
|
|
50
|
+
}}
|
|
51
|
+
class="fixed bottom-5 right-5 z-[2147483647] flex items-center gap-1 pl-[10px] pr-[14px] h-[32px] rounded-xl font-sans font-semibold text-[12px] text-accent bg-header border border-border cursor-pointer select-none shadow-[0_8px_32px_rgba(0,0,0,0.7)] transition-all duration-200 hover:border-accent hover:bg-soft hover:shadow-[0_8px_32px_rgba(56,189,248,0.1)]"
|
|
52
|
+
>
|
|
53
|
+
<img class="h-[13px] w-[13px]" src={logo} />
|
|
54
|
+
<span class="text-[10px] font-bold text-accent tracking-widest uppercase">
|
|
55
|
+
devtools
|
|
56
|
+
</span>
|
|
57
|
+
</button>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const DevTools = {
|
|
65
|
+
plugins: [...DEFAULT_PLUGINS] as DevtoolsPlugin[],
|
|
66
|
+
host: null as HTMLElement | null,
|
|
67
|
+
cleanup: null as (() => void) | null,
|
|
68
|
+
|
|
69
|
+
init(options: DevToolsOptions = {}) {
|
|
70
|
+
if (this.host) return;
|
|
71
|
+
|
|
72
|
+
if (options.plugins) {
|
|
73
|
+
this.plugins = options.plugins;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.plugins.forEach((p) => p.setup?.(Registry.instance));
|
|
77
|
+
|
|
78
|
+
const host = document.createElement("div");
|
|
79
|
+
host.id = "praxisjs-devtools";
|
|
80
|
+
document.body.appendChild(host);
|
|
81
|
+
this.host = host;
|
|
82
|
+
|
|
83
|
+
const shadow = host.attachShadow({ mode: "open" });
|
|
84
|
+
|
|
85
|
+
const styleEl = document.createElement("style");
|
|
86
|
+
styleEl.textContent = unoReset + unoStyles;
|
|
87
|
+
shadow.appendChild(styleEl);
|
|
88
|
+
|
|
89
|
+
const container = document.createElement("div");
|
|
90
|
+
shadow.appendChild(container);
|
|
91
|
+
|
|
92
|
+
this.cleanup = render(
|
|
93
|
+
<DevToolsApp plugins={this.plugins} registry={Registry.instance} />,
|
|
94
|
+
container,
|
|
95
|
+
);
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
registerPlugin(plugin: DevtoolsPlugin) {
|
|
99
|
+
if (this.plugins.find((p) => p.id === plugin.id)) return;
|
|
100
|
+
plugin.setup?.(Registry.instance);
|
|
101
|
+
this.plugins.push(plugin);
|
|
102
|
+
this.remount();
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
get registry() {
|
|
106
|
+
return Registry.instance;
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
remount() {
|
|
110
|
+
if (!this.host) return;
|
|
111
|
+
|
|
112
|
+
const shadow = this.host.shadowRoot as ShadowRoot;
|
|
113
|
+
const container = shadow.querySelector("div:last-child") as HTMLElement;
|
|
114
|
+
|
|
115
|
+
this.cleanup?.();
|
|
116
|
+
this.cleanup = render(
|
|
117
|
+
<DevToolsApp plugins={this.plugins} registry={Registry.instance} />,
|
|
118
|
+
container,
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
};
|
package/src/ui/panel.tsx
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import logo from "@assets/logo.svg";
|
|
2
|
+
import {
|
|
3
|
+
EllipsisVerticalIcon,
|
|
4
|
+
PanelBottomIcon,
|
|
5
|
+
PanelLeftIcon,
|
|
6
|
+
PanelRightIcon,
|
|
7
|
+
PanelTopIcon,
|
|
8
|
+
XIcon,
|
|
9
|
+
} from "@icons";
|
|
10
|
+
import { IconButton } from "@shared/icon-button";
|
|
11
|
+
|
|
12
|
+
import { signal } from "@praxisjs/core";
|
|
13
|
+
import type { Component } from "@praxisjs/shared";
|
|
14
|
+
|
|
15
|
+
import type { Registry } from "@core/registry";
|
|
16
|
+
import type { DevtoolsPlugin } from "@plugins/types";
|
|
17
|
+
|
|
18
|
+
interface PanelProps {
|
|
19
|
+
plugins: DevtoolsPlugin[];
|
|
20
|
+
registry: Registry;
|
|
21
|
+
onClose: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type Dock = "bottom" | "top" | "left" | "right";
|
|
25
|
+
|
|
26
|
+
const MIN_HEIGHT = 180;
|
|
27
|
+
const DEFAULT_HEIGHT = 300;
|
|
28
|
+
const MIN_WIDTH = 240;
|
|
29
|
+
const DEFAULT_WIDTH = 380;
|
|
30
|
+
|
|
31
|
+
const DOCK_ICONS: Record<Dock, Component> = {
|
|
32
|
+
bottom: PanelBottomIcon,
|
|
33
|
+
top: PanelTopIcon,
|
|
34
|
+
left: PanelLeftIcon,
|
|
35
|
+
right: PanelRightIcon,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function Panel({ plugins, registry, onClose }: PanelProps) {
|
|
39
|
+
const activeTab = signal(plugins[0]?.id ?? "");
|
|
40
|
+
const dock = signal<Dock>("bottom");
|
|
41
|
+
const dockMenuOpen = signal(false);
|
|
42
|
+
const bottomSize = signal(DEFAULT_HEIGHT);
|
|
43
|
+
const vertSize = signal(DEFAULT_WIDTH);
|
|
44
|
+
|
|
45
|
+
const handleMouseDown = (e: MouseEvent) => {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
const startX = e.clientX;
|
|
48
|
+
const startY = e.clientY;
|
|
49
|
+
const d = dock();
|
|
50
|
+
const startSize = d === "bottom" || d === "top" ? bottomSize() : vertSize();
|
|
51
|
+
|
|
52
|
+
const onMove = (ev: MouseEvent) => {
|
|
53
|
+
const d = dock();
|
|
54
|
+
if (d === "bottom") {
|
|
55
|
+
bottomSize.set(
|
|
56
|
+
Math.max(
|
|
57
|
+
MIN_HEIGHT,
|
|
58
|
+
Math.min(
|
|
59
|
+
window.innerHeight * 0.85,
|
|
60
|
+
startSize + (startY - ev.clientY),
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
} else if (d === "top") {
|
|
65
|
+
bottomSize.set(
|
|
66
|
+
Math.max(
|
|
67
|
+
MIN_HEIGHT,
|
|
68
|
+
Math.min(
|
|
69
|
+
window.innerHeight * 0.85,
|
|
70
|
+
startSize + (ev.clientY - startY),
|
|
71
|
+
),
|
|
72
|
+
),
|
|
73
|
+
);
|
|
74
|
+
} else if (d === "left") {
|
|
75
|
+
vertSize.set(
|
|
76
|
+
Math.max(
|
|
77
|
+
MIN_WIDTH,
|
|
78
|
+
Math.min(
|
|
79
|
+
window.innerWidth * 0.6,
|
|
80
|
+
startSize + (ev.clientX - startX),
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
} else {
|
|
85
|
+
vertSize.set(
|
|
86
|
+
Math.max(
|
|
87
|
+
MIN_WIDTH,
|
|
88
|
+
Math.min(
|
|
89
|
+
window.innerWidth * 0.6,
|
|
90
|
+
startSize + (startX - ev.clientX),
|
|
91
|
+
),
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const onUp = () => {
|
|
98
|
+
document.removeEventListener("mousemove", onMove);
|
|
99
|
+
document.removeEventListener("mouseup", onUp);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
document.addEventListener("mousemove", onMove);
|
|
103
|
+
document.addEventListener("mouseup", onUp);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div
|
|
108
|
+
class={() => {
|
|
109
|
+
const d = dock();
|
|
110
|
+
if (d === "top")
|
|
111
|
+
return "flex flex-col-reverse fixed top-0 left-0 w-full z-[2147483647]";
|
|
112
|
+
if (d === "left")
|
|
113
|
+
return "flex flex-row-reverse fixed left-0 top-0 h-screen z-[2147483647]";
|
|
114
|
+
if (d === "right")
|
|
115
|
+
return "flex fixed right-0 top-0 h-screen z-[2147483647]";
|
|
116
|
+
return "flex flex-col fixed bottom-0 left-0 w-full z-[2147483647]";
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
<div
|
|
120
|
+
class={() => {
|
|
121
|
+
const d = dock();
|
|
122
|
+
if (d === "bottom" || d === "top")
|
|
123
|
+
return "h-1 cursor-[ns-resize] bg-transparent hover:bg-accent transition-colors duration-200";
|
|
124
|
+
return "w-1 cursor-[ew-resize] bg-transparent hover:bg-accent transition-colors duration-200";
|
|
125
|
+
}}
|
|
126
|
+
onMouseDown={handleMouseDown}
|
|
127
|
+
/>
|
|
128
|
+
<div
|
|
129
|
+
class={() => {
|
|
130
|
+
const d = dock();
|
|
131
|
+
if (d === "top") return "w-full h-full bg-bg border-b border-border";
|
|
132
|
+
if (d === "left") return "w-full h-full bg-bg border-r border-border";
|
|
133
|
+
if (d === "right")
|
|
134
|
+
return "w-full h-full bg-bg border-l border-border";
|
|
135
|
+
return "w-full h-full bg-bg border-t border-border";
|
|
136
|
+
}}
|
|
137
|
+
style={() => {
|
|
138
|
+
const d = dock();
|
|
139
|
+
return d === "bottom" || d === "top"
|
|
140
|
+
? { height: `${bottomSize().toString()}px` }
|
|
141
|
+
: { width: `${vertSize().toString()}px` };
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
<div class="flex flex-col flex-1 w-full h-full overflow-hidden">
|
|
145
|
+
<div class="flex h-10 items-stretch border-b border-border bg-header shrink-0 px-2 gap-[2px]">
|
|
146
|
+
<div class="flex items-center gap-[6px] pr-3 mr-[2px] border-r border-border shrink-0">
|
|
147
|
+
<img class="h-[13px] w-[13px]" src={logo} />
|
|
148
|
+
<span class="text-[10px] font-bold text-accent tracking-widest uppercase">
|
|
149
|
+
praxisjs
|
|
150
|
+
</span>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div class="flex items-stretch flex-1">
|
|
154
|
+
{plugins.map((p) => (
|
|
155
|
+
<button
|
|
156
|
+
key={p.id}
|
|
157
|
+
class={() =>
|
|
158
|
+
activeTab() === p.id
|
|
159
|
+
? "relative flex items-center px-3 text-[11px] font-semibold text-accent cursor-pointer"
|
|
160
|
+
: "flex items-center px-3 text-[11px] text-muted cursor-pointer hover:text-text transition-colors duration-150"
|
|
161
|
+
}
|
|
162
|
+
onClick={() => {
|
|
163
|
+
activeTab.set(p.id);
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
{p.label}
|
|
167
|
+
{() =>
|
|
168
|
+
activeTab() === p.id ? (
|
|
169
|
+
<span class="absolute bottom-0 left-0 right-0 h-[2px] bg-accent" />
|
|
170
|
+
) : null
|
|
171
|
+
}
|
|
172
|
+
</button>
|
|
173
|
+
))}
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div class="flex items-center gap-[2px] shrink-0">
|
|
177
|
+
<div class="relative">
|
|
178
|
+
<IconButton
|
|
179
|
+
title="Dock position"
|
|
180
|
+
icon={EllipsisVerticalIcon}
|
|
181
|
+
active={() => dockMenuOpen()}
|
|
182
|
+
onClick={() => {
|
|
183
|
+
dockMenuOpen.set(!dockMenuOpen());
|
|
184
|
+
}}
|
|
185
|
+
/>
|
|
186
|
+
{() =>
|
|
187
|
+
dockMenuOpen() ? (
|
|
188
|
+
<div class="absolute right-0 top-full mt-1 flex gap-[2px] bg-bg border border-border rounded-md shadow-lg p-1 z-10">
|
|
189
|
+
{(["bottom", "top", "left", "right"] as Dock[]).map(
|
|
190
|
+
(v) => (
|
|
191
|
+
<IconButton
|
|
192
|
+
key={v}
|
|
193
|
+
title={`Dock ${v}`}
|
|
194
|
+
active={() => dock() === v}
|
|
195
|
+
icon={DOCK_ICONS[v]}
|
|
196
|
+
onClick={() => {
|
|
197
|
+
dock.set(v);
|
|
198
|
+
dockMenuOpen.set(false);
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
201
|
+
),
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
) : null
|
|
205
|
+
}
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div class="w-px h-3 bg-border mx-1" />
|
|
209
|
+
|
|
210
|
+
<IconButton title="Close" icon={XIcon} onClick={onClose} />
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<div class="flex-1 overflow-hidden flex flex-col">
|
|
215
|
+
{() => {
|
|
216
|
+
const plugin = plugins.find((p) => p.id === activeTab());
|
|
217
|
+
const Active = plugin?.component;
|
|
218
|
+
return Active ? <Active registry={registry} /> : null;
|
|
219
|
+
}}
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function EmptyState({ message }: { message: string }) {
|
|
2
|
+
return (
|
|
3
|
+
<div class="flex flex-col items-center justify-center gap-2 py-12">
|
|
4
|
+
<span class="text-subtle text-[20px] leading-none select-none font-mono">
|
|
5
|
+
—
|
|
6
|
+
</span>
|
|
7
|
+
<p class="text-subtle text-[11px] text-center leading-relaxed max-w-[200px]">
|
|
8
|
+
{message}
|
|
9
|
+
</p>
|
|
10
|
+
</div>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Component } from "@praxisjs/shared";
|
|
2
|
+
|
|
3
|
+
export function IconButton({
|
|
4
|
+
title,
|
|
5
|
+
active,
|
|
6
|
+
icon: Icon,
|
|
7
|
+
onClick,
|
|
8
|
+
}: {
|
|
9
|
+
title?: string;
|
|
10
|
+
active?: boolean | (() => boolean);
|
|
11
|
+
icon: Component;
|
|
12
|
+
onClick: () => void;
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<button
|
|
16
|
+
title={title}
|
|
17
|
+
onClick={onClick}
|
|
18
|
+
class={() => {
|
|
19
|
+
const isActive = typeof active === "function" ? active() : active;
|
|
20
|
+
return `w-[22px] h-[22px] flex items-center justify-center rounded-md text-[11px] cursor-pointer transition-colors duration-150 ${
|
|
21
|
+
isActive
|
|
22
|
+
? "text-accent bg-soft color-white"
|
|
23
|
+
: "text-subtle hover:color-muted hover:bg-section"
|
|
24
|
+
}`;
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
<Icon />
|
|
28
|
+
</button>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Children } from "@praxisjs/shared";
|
|
2
|
+
|
|
3
|
+
export function PanelSection({
|
|
4
|
+
label,
|
|
5
|
+
children,
|
|
6
|
+
}: {
|
|
7
|
+
label: string;
|
|
8
|
+
children?: Children;
|
|
9
|
+
}) {
|
|
10
|
+
return (
|
|
11
|
+
<div class="border-b border-border">
|
|
12
|
+
<div class="px-3 py-[4px] text-[9px] text-subtle font-bold tracking-[0.12em] uppercase bg-section border-b border-border">
|
|
13
|
+
{label}
|
|
14
|
+
</div>
|
|
15
|
+
{children}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function SearchInput({
|
|
2
|
+
placeholder,
|
|
3
|
+
onInput,
|
|
4
|
+
}: {
|
|
5
|
+
placeholder: string;
|
|
6
|
+
onInput: (value: string) => void;
|
|
7
|
+
}) {
|
|
8
|
+
return (
|
|
9
|
+
<input
|
|
10
|
+
type="text"
|
|
11
|
+
placeholder={placeholder}
|
|
12
|
+
class="w-full bg-input border border-border rounded-lg text-text text-[11px] px-3 py-[5px] outline-none box-border transition-shadow duration-150"
|
|
13
|
+
onInput={(e: InputEvent) => {
|
|
14
|
+
onInput((e.target as HTMLInputElement).value);
|
|
15
|
+
}}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Children } from "@praxisjs/shared";
|
|
2
|
+
|
|
3
|
+
export function SidePanel({
|
|
4
|
+
children,
|
|
5
|
+
width = "280px",
|
|
6
|
+
}: {
|
|
7
|
+
children?: Children;
|
|
8
|
+
width?: string;
|
|
9
|
+
}) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
class="shrink-0 border-l border-border flex flex-col overflow-hidden"
|
|
13
|
+
style={{ width }}
|
|
14
|
+
>
|
|
15
|
+
{children}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function time(ts: number, prefix?: string): string {
|
|
2
|
+
const localPrefix = prefix ? ` ${prefix}` : "";
|
|
3
|
+
const diff = Date.now() - ts;
|
|
4
|
+
if (diff < 1000) return `${diff.toString()}ms${localPrefix}`;
|
|
5
|
+
if (diff < 60_000) return `${(diff / 1000).toFixed(1)}s${localPrefix}`;
|
|
6
|
+
return `${Math.floor(diff / 60_000).toString()}m${localPrefix}`;
|
|
7
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function formatValue(value: unknown): string {
|
|
2
|
+
if (value === null) return "null";
|
|
3
|
+
if (value === undefined) return "undefined";
|
|
4
|
+
if (typeof value === "string") return `"${value}"`;
|
|
5
|
+
if (typeof value === "object") {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.stringify(value);
|
|
8
|
+
} catch {
|
|
9
|
+
return "[object]";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return String(value as unknown);
|
|
13
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"rootDir": "./",
|
|
6
|
+
"jsxImportSource": "@praxisjs/jsx",
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"paths": {
|
|
10
|
+
"@/*": ["./src/*"],
|
|
11
|
+
"@shared/*": ["./src/ui/shared/*"],
|
|
12
|
+
"@assets/*": ["./assets/*"],
|
|
13
|
+
"@core/*": ["./src/core/*"],
|
|
14
|
+
"@plugins/*": ["./src/plugins/*"],
|
|
15
|
+
"@decorators/*": ["./src/decorators/*"],
|
|
16
|
+
"@utils/*": ["./src/utils/*"],
|
|
17
|
+
"@icons": ["./src/icons/index.ts"]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"include": ["src", "uno.config.ts", "vite.config.ts"]
|
|
21
|
+
}
|
package/uno.config.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import presetWind3 from "@unocss/preset-wind3";
|
|
2
|
+
import { defineConfig } from "unocss";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
presets: [presetWind3()],
|
|
6
|
+
|
|
7
|
+
theme: {
|
|
8
|
+
colors: {
|
|
9
|
+
bg: "#0f0d17",
|
|
10
|
+
header: "#161326",
|
|
11
|
+
section: "#1b1830",
|
|
12
|
+
border: "rgba(155,144,230,0.25)",
|
|
13
|
+
|
|
14
|
+
text: "#f4f2ff",
|
|
15
|
+
muted: "#c7c3e6",
|
|
16
|
+
subtle: "#6f6a8f",
|
|
17
|
+
|
|
18
|
+
selected: "#221e3d",
|
|
19
|
+
input: "#1b1830",
|
|
20
|
+
|
|
21
|
+
accent: "#9b90e6",
|
|
22
|
+
accent2: "#7c6dd6",
|
|
23
|
+
accent3: "#6d5bbd",
|
|
24
|
+
soft: "rgba(155,144,230,0.18)",
|
|
25
|
+
|
|
26
|
+
success: "#0ea57a",
|
|
27
|
+
warn: "#d97706",
|
|
28
|
+
error: "#dc2626",
|
|
29
|
+
important: "#9b90e6",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
preflights: [
|
|
34
|
+
{
|
|
35
|
+
getCSS: () => `
|
|
36
|
+
:host { all: initial; display: block; font-family: Inter, ui-sans-serif, system-ui, -apple-system, sans-serif; }
|
|
37
|
+
* { box-sizing: border-box; }
|
|
38
|
+
|
|
39
|
+
input::placeholder { color: #6f6a8f; }
|
|
40
|
+
input:focus {
|
|
41
|
+
outline: none;
|
|
42
|
+
box-shadow: 0 0 0 2px rgba(155,144,230,0.3);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
::-webkit-scrollbar { width: 4px; height: 4px; }
|
|
46
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
47
|
+
::-webkit-scrollbar-thumb {
|
|
48
|
+
background: rgba(155,144,230,0.2);
|
|
49
|
+
border-radius: 2px;
|
|
50
|
+
}
|
|
51
|
+
::-webkit-scrollbar-thumb:hover {
|
|
52
|
+
background: rgba(155,144,230,0.4);
|
|
53
|
+
}
|
|
54
|
+
`,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
});
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
|
|
3
|
+
import UnoCSS from "unocss/vite";
|
|
4
|
+
import { defineConfig } from "vite";
|
|
5
|
+
import dts from "vite-plugin-dts";
|
|
6
|
+
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
plugins: [UnoCSS(), dts({ rollupTypes: true })],
|
|
9
|
+
esbuild: {
|
|
10
|
+
jsxImportSource: "@praxisjs/jsx",
|
|
11
|
+
jsx: "automatic",
|
|
12
|
+
},
|
|
13
|
+
build: {
|
|
14
|
+
lib: {
|
|
15
|
+
entry: "src/index.ts",
|
|
16
|
+
formats: ["es"],
|
|
17
|
+
fileName: "index",
|
|
18
|
+
},
|
|
19
|
+
rollupOptions: {
|
|
20
|
+
external: [
|
|
21
|
+
"@praxisjs/core",
|
|
22
|
+
"@praxisjs/jsx",
|
|
23
|
+
"@praxisjs/jsx/jsx-runtime",
|
|
24
|
+
"@praxisjs/runtime",
|
|
25
|
+
"@praxisjs/shared",
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
cssCodeSplit: false,
|
|
29
|
+
},
|
|
30
|
+
resolve: {
|
|
31
|
+
alias: {
|
|
32
|
+
"@": path.resolve(__dirname, "src"),
|
|
33
|
+
"@shared": path.resolve(__dirname, "src/ui/shared"),
|
|
34
|
+
"@assets": path.resolve(__dirname, "assets"),
|
|
35
|
+
"@core": path.resolve(__dirname, "src/core"),
|
|
36
|
+
"@plugins": path.resolve(__dirname, "src/plugins"),
|
|
37
|
+
"@decorators": path.resolve(__dirname, "src/decorators"),
|
|
38
|
+
"@utils": path.resolve(__dirname, "src/utils"),
|
|
39
|
+
"@icons": path.resolve(__dirname, "src/icons"),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|