Files
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

182 lines
5.7 KiB
TypeScript

"use client";
import { useState } from "react";
import {
useAui,
AuiProvider,
useAuiState,
useAuiEvent,
} from "@assistant-ui/store";
import { FooList, FooListResource } from "./store/foo-store";
/**
* Single Foo component - displays and allows editing a single foo
*/
const Foo = () => {
const aui = useAui();
const fooId = useAuiState((s) => s.foo.id);
const fooBar = useAuiState((s) => s.foo.bar);
// Each foo logs its own events - only receives events from THIS foo instance
useAuiEvent("foo.updated", (payload) => {
console.log(`[${fooId}] Updated to: ${payload.newValue}`);
});
const handleUpdate = () => {
aui.foo().updateBar(`Updated at ${new Date().toLocaleTimeString()}`);
};
return (
<div className="rounded-lg border border-gray-200 bg-white p-6 shadow-md transition-shadow hover:shadow-lg dark:border-gray-700 dark:bg-gray-800">
<div className="space-y-3">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-500 dark:text-gray-400">
ID:
</span>
<span className="font-mono text-sm text-gray-900 dark:text-white">
{fooId}
</span>
</div>
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-500 dark:text-gray-400">
Value:
</span>
<span className="text-gray-900 dark:text-white">{fooBar}</span>
</div>
<div className="mt-2 flex gap-2">
<button
type="button"
onClick={handleUpdate}
className="flex-1 rounded-md bg-blue-600 px-4 py-2 font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none dark:focus:ring-offset-gray-800"
>
Update
</button>
<button
type="button"
onClick={() => aui.foo().remove()}
className="rounded-md bg-red-600 px-4 py-2 font-medium text-white transition-colors hover:bg-red-700 focus:ring-2 focus:ring-red-500 focus:ring-offset-2 focus:outline-none dark:focus:ring-offset-gray-800"
>
Delete
</button>
</div>
</div>
</div>
);
};
const FooListLength = () => {
const fooListLength = useAuiState((s) => s.fooList.foos.length);
return (
<span className="text-gray-500 dark:text-gray-400">
({fooListLength} items)
</span>
);
};
const AddFooButton = () => {
const aui = useAui();
return (
<button
type="button"
onClick={() => aui.fooList().addFoo()}
className="rounded-md bg-green-600 px-4 py-2 font-medium text-white transition-colors hover:bg-green-700 focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:outline-none dark:focus:ring-offset-gray-800"
>
Add New
</button>
);
};
type EventLogEntry = {
id: number;
event: string;
payload: unknown;
timestamp: Date;
};
let idCounter = 0;
/**
* EventLog component - demonstrates event subscription
*/
const EventLog = () => {
const [logs, setLogs] = useState<EventLogEntry[]>([]);
// Subscribe to all events using the wildcard selector
useAuiEvent("*", (data) => {
setLogs((prev) => [
{
id: ++idCounter,
event: data.event,
payload: data.payload,
timestamp: new Date(),
},
...prev.slice(0, 9), // Keep last 10 entries
]);
});
return (
<div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800">
<h3 className="mb-3 font-semibold text-gray-900 dark:text-white">
Event Log
</h3>
<div className="max-h-48 space-y-2 overflow-y-auto">
{logs.length === 0 ? (
<p className="text-sm text-gray-500 dark:text-gray-400">
No events yet. Try updating or deleting a foo.
</p>
) : (
logs.map((log) => (
<div
key={log.id}
className="rounded border border-gray-100 bg-gray-50 p-2 font-mono text-xs dark:border-gray-600 dark:bg-gray-700"
>
<span className="font-semibold text-blue-600 dark:text-blue-400">
{log.event}
</span>
<span className="ml-2 text-gray-600 dark:text-gray-300">
{JSON.stringify(log.payload)}
</span>
<span className="ml-2 text-gray-400 dark:text-gray-500">
{log.timestamp.toLocaleTimeString()}
</span>
</div>
))
)}
</div>
</div>
);
};
/**
* Example App - demonstrates the store with styled components
*
* Note: The fooList scope is also registered in foo-scope.ts as a default,
* but we're explicitly passing it here for clarity in the example.
*/
export const ExampleApp = () => {
const aui = useAui({
fooList: FooListResource({ initialValues: true }),
});
return (
<AuiProvider value={aui}>
<div className="space-y-6">
<div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800">
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Foo List <FooListLength />
</h2>
<AddFooButton />
</div>
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
Each item is rendered in its own FooProvider with scoped access
</p>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<FooList>{() => <Foo />}</FooList>
</div>
<EventLog />
</div>
</AuiProvider>
);
};