@parlor/scaffold 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +11 -2
- package/dist/add/addon.d.ts +103 -0
- package/dist/add/addon.d.ts.map +1 -0
- package/dist/add/addon.js +12 -0
- package/dist/add/addon.js.map +1 -0
- package/dist/add/plan.d.ts +72 -0
- package/dist/add/plan.d.ts.map +1 -0
- package/dist/add/plan.js +237 -0
- package/dist/add/plan.js.map +1 -0
- package/dist/add/run.d.ts +92 -0
- package/dist/add/run.d.ts.map +1 -0
- package/dist/add/run.js +167 -0
- package/dist/add/run.js.map +1 -0
- package/dist/add/transaction.d.ts +67 -0
- package/dist/add/transaction.d.ts.map +1 -0
- package/dist/add/transaction.js +181 -0
- package/dist/add/transaction.js.map +1 -0
- package/dist/addons/agent.d.ts +58 -0
- package/dist/addons/agent.d.ts.map +1 -0
- package/dist/addons/agent.js +50 -0
- package/dist/addons/agent.js.map +1 -0
- package/dist/addons/auth.d.ts +103 -0
- package/dist/addons/auth.d.ts.map +1 -0
- package/dist/addons/auth.js +93 -0
- package/dist/addons/auth.js.map +1 -0
- package/dist/addons/index.d.ts +159 -0
- package/dist/addons/index.d.ts.map +1 -0
- package/dist/addons/index.js +8 -0
- package/dist/addons/index.js.map +1 -0
- package/dist/catalog.d.ts +3 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +23 -0
- package/dist/catalog.js.map +1 -0
- package/dist/create.d.ts +45 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +162 -0
- package/dist/create.js.map +1 -0
- package/dist/docs.d.ts +12 -0
- package/dist/docs.d.ts.map +1 -0
- package/dist/docs.js +70 -0
- package/dist/docs.js.map +1 -0
- package/dist/edit/inspect.d.ts +15 -0
- package/dist/edit/inspect.d.ts.map +1 -0
- package/dist/edit/inspect.js +73 -0
- package/dist/edit/inspect.js.map +1 -0
- package/dist/edit/ops.d.ts +92 -0
- package/dist/edit/ops.d.ts.map +1 -0
- package/dist/edit/ops.js +516 -0
- package/dist/edit/ops.js.map +1 -0
- package/dist/edit/source.d.ts +24 -0
- package/dist/edit/source.d.ts.map +1 -0
- package/dist/edit/source.js +36 -0
- package/dist/edit/source.js.map +1 -0
- package/dist/edit/syntax.d.ts +39 -0
- package/dist/edit/syntax.d.ts.map +1 -0
- package/dist/edit/syntax.js +129 -0
- package/dist/edit/syntax.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +5 -0
- package/dist/errors.js.map +1 -0
- package/dist/files.d.ts +15 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +65 -0
- package/dist/files.js.map +1 -0
- package/dist/fixtures.d.ts +28 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +66 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/git.d.ts +22 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +56 -0
- package/dist/git.js.map +1 -0
- package/dist/identity.d.ts +9 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +23 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/package-manager.d.ts +49 -0
- package/dist/package-manager.d.ts.map +1 -0
- package/dist/package-manager.js +118 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/version.d.ts +6 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +9 -0
- package/dist/version.js.map +1 -0
- package/package.json +46 -3
- package/templates/auth/auth.ts +19 -0
- package/templates/auth/schema/auth.ts +1 -0
- package/templates/auth/src/auth/client.ts +3 -0
- package/templates/auth/src/auth/sign-in-gate.tsx +17 -0
- package/templates/auth/src/auth/sign-out-button.tsx +17 -0
- package/templates/auth/src/routes/link.tsx +92 -0
- package/templates/auth/src/routes/sign-in.tsx +117 -0
- package/templates/default/_gitignore +7 -0
- package/templates/default/actions/index.ts +58 -0
- package/templates/default/actions/todos.spec.ts +38 -0
- package/templates/default/index.html +13 -0
- package/templates/default/migrations/0000_init.sql +9 -0
- package/templates/default/migrations/meta/0000_snapshot.json +87 -0
- package/templates/default/migrations/meta/_journal.json +13 -0
- package/templates/default/package.json +44 -0
- package/templates/default/parlor.config.ts +12 -0
- package/templates/default/schema/index.ts +1 -0
- package/templates/default/schema/todos.ts +13 -0
- package/templates/default/server.ts +13 -0
- package/templates/default/src/agent/agent-button.tsx +120 -0
- package/templates/default/src/agent/agent-sidebar.tsx +828 -0
- package/templates/default/src/agent/agent.css +5 -0
- package/templates/default/src/agent/confirmation-card.tsx +42 -0
- package/templates/default/src/agent/sidebar-view.ts +36 -0
- package/templates/default/src/agent/views.ts +9 -0
- package/templates/default/src/app.css +1 -0
- package/templates/default/src/header-actions.tsx +9 -0
- package/templates/default/src/main.tsx +27 -0
- package/templates/default/src/parlor.d.ts +9 -0
- package/templates/default/src/routeTree.gen.ts +59 -0
- package/templates/default/src/routes/__root.tsx +29 -0
- package/templates/default/src/routes/index.tsx +141 -0
- package/templates/default/tsconfig.json +8 -0
- package/templates/default/vitest.config.ts +5 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ActionError, defineAction } from "@parlor/core";
|
|
2
|
+
import { createRegistry } from "@parlor/core/registry";
|
|
3
|
+
import { and, asc, eq } from "drizzle-orm";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
import { todos } from "../schema/index.js";
|
|
7
|
+
|
|
8
|
+
const mine = (id: string, scope: string) => and(eq(todos.id, id), eq(todos.scope, scope));
|
|
9
|
+
|
|
10
|
+
export const registry = createRegistry({
|
|
11
|
+
listTodos: defineAction({
|
|
12
|
+
description: "List the todos, oldest first.",
|
|
13
|
+
input: z.object({}),
|
|
14
|
+
reads: [todos],
|
|
15
|
+
run: (_, { db, scope }) =>
|
|
16
|
+
db.select().from(todos).where(eq(todos.scope, scope)).orderBy(asc(todos.createdAt)),
|
|
17
|
+
}),
|
|
18
|
+
addTodo: defineAction({
|
|
19
|
+
description: "Add a todo.",
|
|
20
|
+
input: z.object({
|
|
21
|
+
title: z.string().trim().min(1).describe("What needs to be done"),
|
|
22
|
+
}),
|
|
23
|
+
writes: [todos],
|
|
24
|
+
run: async ({ title }, { db, scope }) => {
|
|
25
|
+
const [todo] = await db
|
|
26
|
+
.insert(todos)
|
|
27
|
+
.values({ id: crypto.randomUUID(), scope, title })
|
|
28
|
+
.returning();
|
|
29
|
+
return todo;
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
32
|
+
toggleTodo: defineAction({
|
|
33
|
+
description: "Mark a todo done, or not done again.",
|
|
34
|
+
input: z.object({ id: z.string() }),
|
|
35
|
+
writes: [todos],
|
|
36
|
+
run: async ({ id }, { db, scope }) => {
|
|
37
|
+
const [current] = await db.select().from(todos).where(mine(id, scope));
|
|
38
|
+
if (!current) throw new ActionError("not_found", `No todo ${id}`);
|
|
39
|
+
const [todo] = await db
|
|
40
|
+
.update(todos)
|
|
41
|
+
.set({ done: !current.done })
|
|
42
|
+
.where(mine(id, scope))
|
|
43
|
+
.returning();
|
|
44
|
+
return todo;
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
deleteTodo: defineAction({
|
|
48
|
+
description: "Delete a todo.",
|
|
49
|
+
input: z.object({ id: z.string() }),
|
|
50
|
+
writes: [todos],
|
|
51
|
+
confirm: true,
|
|
52
|
+
run: async ({ id }, { db, scope }) => {
|
|
53
|
+
const [todo] = await db.delete(todos).where(mine(id, scope)).returning();
|
|
54
|
+
if (!todo) throw new ActionError("not_found", `No todo ${id}`);
|
|
55
|
+
return todo;
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
|
|
3
|
+
import { createActionClient } from "@parlor/client";
|
|
4
|
+
import type { ActionsOf } from "@parlor/core/registry";
|
|
5
|
+
import { createDatabase } from "@parlor/data";
|
|
6
|
+
import { createServer } from "@parlor/server";
|
|
7
|
+
import { afterEach, expect, test } from "vitest";
|
|
8
|
+
|
|
9
|
+
import { registry } from "./index.js";
|
|
10
|
+
|
|
11
|
+
const servers: { close(): Promise<void> }[] = [];
|
|
12
|
+
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
await Promise.all(servers.splice(0).map((server) => server.close()));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
async function startApp() {
|
|
18
|
+
const database = await createDatabase({
|
|
19
|
+
dataDir: "memory://",
|
|
20
|
+
migrationsFolder: fileURLToPath(new URL("../migrations", import.meta.url)),
|
|
21
|
+
});
|
|
22
|
+
const server = createServer({ actions: registry, database });
|
|
23
|
+
servers.push(server);
|
|
24
|
+
return createActionClient<ActionsOf<typeof registry>>({
|
|
25
|
+
baseUrl: "http://app.test",
|
|
26
|
+
fetch: (input, init) => server.fetch(new Request(input, init)),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test("a todo added through its action is in the list, not done", async () => {
|
|
31
|
+
const app = await startApp();
|
|
32
|
+
|
|
33
|
+
await app.call("addTodo", { title: "Buy milk" });
|
|
34
|
+
|
|
35
|
+
expect(await app.call("listTodos", {})).toMatchObject([
|
|
36
|
+
{ title: "Buy milk", done: false },
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" href="data:," />
|
|
7
|
+
<link rel="stylesheet" href="/src/app.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
CREATE TABLE "todos" (
|
|
2
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
3
|
+
"scope" text DEFAULT 'default' NOT NULL,
|
|
4
|
+
"title" text NOT NULL,
|
|
5
|
+
"done" boolean DEFAULT false NOT NULL,
|
|
6
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
7
|
+
);
|
|
8
|
+
--> statement-breakpoint
|
|
9
|
+
CREATE INDEX "todos_scope_created_at_idx" ON "todos" USING btree ("scope","created_at");
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "d877853a-ee64-4c03-9a26-1c6dc29be490",
|
|
3
|
+
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
4
|
+
"version": "7",
|
|
5
|
+
"dialect": "postgresql",
|
|
6
|
+
"tables": {
|
|
7
|
+
"public.todos": {
|
|
8
|
+
"name": "todos",
|
|
9
|
+
"schema": "",
|
|
10
|
+
"columns": {
|
|
11
|
+
"id": {
|
|
12
|
+
"name": "id",
|
|
13
|
+
"type": "text",
|
|
14
|
+
"primaryKey": true,
|
|
15
|
+
"notNull": true
|
|
16
|
+
},
|
|
17
|
+
"scope": {
|
|
18
|
+
"name": "scope",
|
|
19
|
+
"type": "text",
|
|
20
|
+
"primaryKey": false,
|
|
21
|
+
"notNull": true,
|
|
22
|
+
"default": "'default'"
|
|
23
|
+
},
|
|
24
|
+
"title": {
|
|
25
|
+
"name": "title",
|
|
26
|
+
"type": "text",
|
|
27
|
+
"primaryKey": false,
|
|
28
|
+
"notNull": true
|
|
29
|
+
},
|
|
30
|
+
"done": {
|
|
31
|
+
"name": "done",
|
|
32
|
+
"type": "boolean",
|
|
33
|
+
"primaryKey": false,
|
|
34
|
+
"notNull": true,
|
|
35
|
+
"default": false
|
|
36
|
+
},
|
|
37
|
+
"created_at": {
|
|
38
|
+
"name": "created_at",
|
|
39
|
+
"type": "timestamp with time zone",
|
|
40
|
+
"primaryKey": false,
|
|
41
|
+
"notNull": true,
|
|
42
|
+
"default": "now()"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"indexes": {
|
|
46
|
+
"todos_scope_created_at_idx": {
|
|
47
|
+
"name": "todos_scope_created_at_idx",
|
|
48
|
+
"columns": [
|
|
49
|
+
{
|
|
50
|
+
"expression": "scope",
|
|
51
|
+
"isExpression": false,
|
|
52
|
+
"asc": true,
|
|
53
|
+
"nulls": "last"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"expression": "created_at",
|
|
57
|
+
"isExpression": false,
|
|
58
|
+
"asc": true,
|
|
59
|
+
"nulls": "last"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"isUnique": false,
|
|
63
|
+
"concurrently": false,
|
|
64
|
+
"method": "btree",
|
|
65
|
+
"with": {}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"foreignKeys": {},
|
|
69
|
+
"compositePrimaryKeys": {},
|
|
70
|
+
"uniqueConstraints": {},
|
|
71
|
+
"policies": {},
|
|
72
|
+
"checkConstraints": {},
|
|
73
|
+
"isRLSEnabled": false
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"enums": {},
|
|
77
|
+
"schemas": {},
|
|
78
|
+
"sequences": {},
|
|
79
|
+
"roles": {},
|
|
80
|
+
"policies": {},
|
|
81
|
+
"views": {},
|
|
82
|
+
"_meta": {
|
|
83
|
+
"columns": {},
|
|
84
|
+
"schemas": {},
|
|
85
|
+
"tables": {}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "parlor-template-default",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "parlor dev",
|
|
8
|
+
"build": "parlor build",
|
|
9
|
+
"typecheck": "parlor typegen && tsc --noEmit",
|
|
10
|
+
"test": "vitest run",
|
|
11
|
+
"db:generate": "parlor db generate",
|
|
12
|
+
"db:migrate": "parlor db migrate"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@parlor/chat": "workspace:*",
|
|
16
|
+
"@parlor/client": "workspace:*",
|
|
17
|
+
"@parlor/core": "workspace:*",
|
|
18
|
+
"@parlor/data": "workspace:*",
|
|
19
|
+
"@parlor/server": "workspace:*",
|
|
20
|
+
"@tanstack/react-query": "catalog:",
|
|
21
|
+
"@tanstack/react-router": "catalog:",
|
|
22
|
+
"drizzle-orm": "^0.45.4",
|
|
23
|
+
"react": "catalog:",
|
|
24
|
+
"react-dom": "catalog:",
|
|
25
|
+
"uqr": "^0.1.3",
|
|
26
|
+
"zod": "catalog:"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@parlor/agent-host": "workspace:*",
|
|
30
|
+
"@parlor/cli": "workspace:*",
|
|
31
|
+
"@parlor/config": "workspace:*",
|
|
32
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
33
|
+
"@types/node": "catalog:",
|
|
34
|
+
"@types/react": "catalog:",
|
|
35
|
+
"@types/react-dom": "catalog:",
|
|
36
|
+
"tailwindcss": "^4.3.3",
|
|
37
|
+
"typescript": "catalog:",
|
|
38
|
+
"vite": "catalog:",
|
|
39
|
+
"vitest": "catalog:"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=24"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { todos } from "./todos.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { boolean, index, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
|
2
|
+
|
|
3
|
+
export const todos = pgTable(
|
|
4
|
+
"todos",
|
|
5
|
+
{
|
|
6
|
+
id: text("id").primaryKey(),
|
|
7
|
+
scope: text("scope").notNull().default("default"),
|
|
8
|
+
title: text("title").notNull(),
|
|
9
|
+
done: boolean("done").notNull().default(false),
|
|
10
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
11
|
+
},
|
|
12
|
+
(table) => [index("todos_scope_created_at_idx").on(table.scope, table.createdAt)],
|
|
13
|
+
);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { chat } from "@parlor/chat/server";
|
|
2
|
+
import { createDatabase } from "@parlor/data";
|
|
3
|
+
import { createServer } from "@parlor/server";
|
|
4
|
+
|
|
5
|
+
import { registry } from "./actions/index.js";
|
|
6
|
+
|
|
7
|
+
const database = await createDatabase({ migrationsFolder: "migrations" });
|
|
8
|
+
|
|
9
|
+
export default createServer({
|
|
10
|
+
actions: registry,
|
|
11
|
+
database,
|
|
12
|
+
plugins: [chat()],
|
|
13
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { useAgentConfirmations, useAgentView } from "@parlor/client/react";
|
|
2
|
+
import {
|
|
3
|
+
lazy,
|
|
4
|
+
startTransition,
|
|
5
|
+
Suspense,
|
|
6
|
+
useCallback,
|
|
7
|
+
useEffect,
|
|
8
|
+
useId,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from "react";
|
|
12
|
+
import { createPortal } from "react-dom";
|
|
13
|
+
|
|
14
|
+
import "./agent.css";
|
|
15
|
+
import { sidebarView } from "./sidebar-view.js";
|
|
16
|
+
import { agentPanel } from "./views.js";
|
|
17
|
+
|
|
18
|
+
const AgentSidebar = lazy(() => import("./agent-sidebar.js"));
|
|
19
|
+
|
|
20
|
+
export function AgentButton() {
|
|
21
|
+
const regionId = useId();
|
|
22
|
+
const titleId = useId();
|
|
23
|
+
const button = useRef<HTMLButtonElement | null>(null);
|
|
24
|
+
const region = useRef<HTMLElement | null>(null);
|
|
25
|
+
const [open, setOpen] = useState(false);
|
|
26
|
+
const [everOpened, setEverOpened] = useState(false);
|
|
27
|
+
const pending = useAgentConfirmations().length;
|
|
28
|
+
const { ref } = useAgentView(agentPanel, { get: () => ({ open }) });
|
|
29
|
+
const attach = useCallback(
|
|
30
|
+
(element: HTMLElement | null) => {
|
|
31
|
+
region.current = element;
|
|
32
|
+
ref(element);
|
|
33
|
+
},
|
|
34
|
+
[ref],
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (open) region.current?.querySelector("textarea")?.focus();
|
|
39
|
+
}, [open]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
sidebarView.set({ open });
|
|
42
|
+
return () => sidebarView.set({ open: false });
|
|
43
|
+
}, [open]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!open) return;
|
|
46
|
+
const dismiss = (event: KeyboardEvent) => {
|
|
47
|
+
if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
|
|
48
|
+
const focus = document.activeElement;
|
|
49
|
+
const inside =
|
|
50
|
+
focus === document.body ||
|
|
51
|
+
focus === button.current ||
|
|
52
|
+
region.current?.contains(focus);
|
|
53
|
+
if (!inside) return;
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
setOpen(false);
|
|
56
|
+
button.current?.focus();
|
|
57
|
+
};
|
|
58
|
+
document.addEventListener("keydown", dismiss);
|
|
59
|
+
return () => document.removeEventListener("keydown", dismiss);
|
|
60
|
+
}, [open]);
|
|
61
|
+
|
|
62
|
+
const toggle = () => {
|
|
63
|
+
startTransition(() => {
|
|
64
|
+
setOpen(!open);
|
|
65
|
+
setEverOpened(true);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const close = () => {
|
|
69
|
+
setOpen(false);
|
|
70
|
+
button.current?.focus();
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<>
|
|
75
|
+
<button
|
|
76
|
+
ref={button}
|
|
77
|
+
type="button"
|
|
78
|
+
aria-expanded={open}
|
|
79
|
+
aria-controls={regionId}
|
|
80
|
+
aria-label={pending > 0 ? `Agent, ${pending} pending` : undefined}
|
|
81
|
+
onClick={toggle}
|
|
82
|
+
className="inline-flex items-center gap-2 rounded-md border border-zinc-300 bg-white px-3 py-1.5 text-sm font-medium hover:bg-zinc-100 aria-expanded:bg-zinc-900 aria-expanded:text-white"
|
|
83
|
+
>
|
|
84
|
+
Agent
|
|
85
|
+
{pending > 0 ? (
|
|
86
|
+
<span className="rounded-full bg-amber-500 px-1.5 text-xs text-white">
|
|
87
|
+
{pending}
|
|
88
|
+
</span>
|
|
89
|
+
) : null}
|
|
90
|
+
</button>
|
|
91
|
+
{createPortal(
|
|
92
|
+
<section
|
|
93
|
+
id={regionId}
|
|
94
|
+
ref={attach}
|
|
95
|
+
aria-labelledby={titleId}
|
|
96
|
+
data-agent-sidebar
|
|
97
|
+
hidden={!open}
|
|
98
|
+
className="fixed inset-y-0 right-0 z-50 flex w-full max-w-md flex-col border-l border-zinc-200 bg-white shadow-xl"
|
|
99
|
+
>
|
|
100
|
+
<header className="flex items-center justify-between border-b border-zinc-200 px-4 py-3">
|
|
101
|
+
<h2 id={titleId} className="font-semibold">
|
|
102
|
+
Agent
|
|
103
|
+
</h2>
|
|
104
|
+
<button
|
|
105
|
+
type="button"
|
|
106
|
+
onClick={close}
|
|
107
|
+
className="rounded-md px-2 py-1 text-sm text-zinc-600 hover:bg-zinc-100"
|
|
108
|
+
>
|
|
109
|
+
Close
|
|
110
|
+
</button>
|
|
111
|
+
</header>
|
|
112
|
+
<Suspense fallback={<p className="p-4 text-sm text-zinc-500">Loading...</p>}>
|
|
113
|
+
{everOpened ? <AgentSidebar /> : null}
|
|
114
|
+
</Suspense>
|
|
115
|
+
</section>,
|
|
116
|
+
document.body,
|
|
117
|
+
)}
|
|
118
|
+
</>
|
|
119
|
+
);
|
|
120
|
+
}
|