@tanstack/cta-framework-react-cra 0.17.0 → 0.17.1

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.
@@ -0,0 +1,74 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+
3
+ import { useChat, useMessages } from '@/hooks/demo.useChat'
4
+
5
+ import Messages from './demo.messages'
6
+
7
+ export default function ChatArea() {
8
+ const messagesEndRef = useRef<HTMLDivElement>(null)
9
+
10
+ const { sendMessage } = useChat()
11
+
12
+ const messages = useMessages()
13
+
14
+ const [message, setMessage] = useState('')
15
+ const [user, setUser] = useState('Alice')
16
+
17
+ useEffect(() => {
18
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
19
+ }, [messages])
20
+
21
+ const postMessage = () => {
22
+ if (message.trim().length) {
23
+ sendMessage(message, user)
24
+ setMessage('')
25
+ }
26
+ }
27
+
28
+ const handleKeyPress = (e: React.KeyboardEvent) => {
29
+ if (e.key === 'Enter') {
30
+ postMessage()
31
+ }
32
+ }
33
+
34
+ return (
35
+ <>
36
+ <div className="flex-1 overflow-y-auto px-4 py-6 space-y-4">
37
+ <Messages messages={messages} user={user} />
38
+ <div ref={messagesEndRef} />
39
+ </div>
40
+
41
+ <div className="bg-white border-t border-gray-200 px-4 py-4">
42
+ <div className="flex items-center space-x-3">
43
+ <select
44
+ value={user}
45
+ onChange={(e) => setUser(e.target.value)}
46
+ className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
47
+ >
48
+ <option value="Alice">Alice</option>
49
+ <option value="Bob">Bob</option>
50
+ </select>
51
+
52
+ <div className="flex-1 relative">
53
+ <input
54
+ type="text"
55
+ value={message}
56
+ onChange={(e) => setMessage(e.target.value)}
57
+ onKeyDown={handleKeyPress}
58
+ placeholder="Type a message..."
59
+ className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
60
+ />
61
+ </div>
62
+
63
+ <button
64
+ onClick={postMessage}
65
+ disabled={message.trim() === ''}
66
+ className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
67
+ >
68
+ Send
69
+ </button>
70
+ </div>
71
+ </div>
72
+ </>
73
+ )
74
+ }
@@ -0,0 +1,68 @@
1
+ import type { Message } from '@/db-collections'
2
+
3
+ export const getAvatarColor = (username: string) => {
4
+ const colors = [
5
+ 'bg-blue-500',
6
+ 'bg-green-500',
7
+ 'bg-purple-500',
8
+ 'bg-pink-500',
9
+ 'bg-indigo-500',
10
+ 'bg-red-500',
11
+ 'bg-yellow-500',
12
+ 'bg-teal-500',
13
+ ]
14
+ const index = username
15
+ .split('')
16
+ .reduce((acc, char) => acc + char.charCodeAt(0), 0)
17
+ return colors[index % colors.length]
18
+ }
19
+
20
+ export default function Messages({
21
+ messages,
22
+ user,
23
+ }: {
24
+ messages: Message[]
25
+ user: string
26
+ }) {
27
+ return (
28
+ <>
29
+ {messages.map((msg: Message) => (
30
+ <div
31
+ key={msg.id}
32
+ className={`flex ${
33
+ msg.user === user ? 'justify-end' : 'justify-start'
34
+ }`}
35
+ >
36
+ <div
37
+ className={`flex items-start space-x-3 max-w-xs lg:max-w-md ${
38
+ msg.user === user ? 'flex-row-reverse space-x-reverse' : ''
39
+ }`}
40
+ >
41
+ <div
42
+ className={`w-8 h-8 rounded-full flex items-center justify-center text-white text-sm font-medium ${getAvatarColor(
43
+ msg.user,
44
+ )}`}
45
+ >
46
+ {msg.user.charAt(0).toUpperCase()}
47
+ </div>
48
+
49
+ <div
50
+ className={`px-4 py-2 rounded-2xl ${
51
+ msg.user === user
52
+ ? 'bg-blue-500 text-white rounded-br-md'
53
+ : 'bg-white text-gray-800 border border-gray-200 rounded-bl-md'
54
+ }`}
55
+ >
56
+ {msg.user !== user && (
57
+ <p className="text-xs text-gray-500 mb-1 font-medium">
58
+ {msg.user}
59
+ </p>
60
+ )}
61
+ <p className="text-sm">{msg.text}</p>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ ))}
66
+ </>
67
+ )
68
+ }
@@ -0,0 +1,20 @@
1
+ import {
2
+ createCollection,
3
+ localOnlyCollectionOptions,
4
+ } from "@tanstack/react-db";
5
+ import { z } from "zod";
6
+
7
+ const MessageSchema = z.object({
8
+ id: z.number(),
9
+ text: z.string(),
10
+ user: z.string(),
11
+ });
12
+
13
+ export type Message = z.infer<typeof MessageSchema>;
14
+
15
+ export const messagesCollection = createCollection(
16
+ localOnlyCollectionOptions({
17
+ getKey: (message) => message.id,
18
+ schema: MessageSchema,
19
+ })
20
+ );
@@ -0,0 +1,62 @@
1
+ import { useEffect, useRef } from 'react'
2
+ import { useLiveQuery } from '@tanstack/react-db'
3
+
4
+ import { messagesCollection, type Message } from '@/db-collections'
5
+
6
+ import type { Collection } from '@tanstack/db'
7
+
8
+ function useStreamConnection(
9
+ url: string,
10
+ collection: Collection<any, any, any>,
11
+ ) {
12
+ const loadedRef = useRef(false)
13
+
14
+ useEffect(() => {
15
+ const fetchData = async () => {
16
+ if (loadedRef.current) return
17
+ loadedRef.current = true
18
+
19
+ const response = await fetch(url)
20
+ const reader = response.body?.getReader()
21
+ if (!reader) {
22
+ return
23
+ }
24
+
25
+ const decoder = new TextDecoder()
26
+ while (true) {
27
+ const { done, value } = await reader.read()
28
+ if (done) break
29
+ for (const chunk of decoder
30
+ .decode(value, { stream: true })
31
+ .split('\n')
32
+ .filter((chunk) => chunk.length > 0)) {
33
+ collection.insert(JSON.parse(chunk))
34
+ }
35
+ }
36
+ }
37
+ fetchData()
38
+ }, [])
39
+ }
40
+
41
+ export function useChat() {
42
+ useStreamConnection('/demo/db-chat-api', messagesCollection)
43
+
44
+ const sendMessage = (message: string, user: string) => {
45
+ fetch('/demo/db-chat-api', {
46
+ method: 'POST',
47
+ body: JSON.stringify({ text: message.trim(), user: user.trim() }),
48
+ })
49
+ }
50
+
51
+ return { sendMessage }
52
+ }
53
+
54
+ export function useMessages() {
55
+ const { data: messages } = useLiveQuery((q) =>
56
+ q.from({ message: messagesCollection }).select(({ message }) => ({
57
+ ...message,
58
+ })),
59
+ )
60
+
61
+ return messages as Message[]
62
+ }
@@ -0,0 +1,74 @@
1
+ import { createServerFileRoute } from '@tanstack/react-start/server'
2
+
3
+ import { createCollection, localOnlyCollectionOptions } from '@tanstack/db'
4
+ import { z } from 'zod'
5
+
6
+ const IncomingMessageSchema = z.object({
7
+ user: z.string(),
8
+ text: z.string(),
9
+ })
10
+
11
+ const MessageSchema = IncomingMessageSchema.extend({
12
+ id: z.number(),
13
+ })
14
+
15
+ export type Message = z.infer<typeof MessageSchema>
16
+
17
+ export const serverMessagesCollection = createCollection(
18
+ localOnlyCollectionOptions({
19
+ getKey: (message) => message.id,
20
+ schema: MessageSchema,
21
+ }),
22
+ )
23
+
24
+ let id = 0
25
+ serverMessagesCollection.insert({
26
+ id: id++,
27
+ user: 'Alice',
28
+ text: 'Hello, how are you?',
29
+ })
30
+ serverMessagesCollection.insert({
31
+ id: id++,
32
+ user: 'Bob',
33
+ text: "I'm fine, thank you!",
34
+ })
35
+
36
+ const sendMessage = (message: { user: string; text: string }) => {
37
+ serverMessagesCollection.insert({
38
+ id: id++,
39
+ user: message.user,
40
+ text: message.text,
41
+ })
42
+ }
43
+
44
+ export const ServerRoute = createServerFileRoute('/demo/db-chat-api').methods({
45
+ GET: () => {
46
+ const stream = new ReadableStream({
47
+ start(controller) {
48
+ for (const [_id, message] of serverMessagesCollection.state) {
49
+ controller.enqueue(JSON.stringify(message) + '\n')
50
+ }
51
+ serverMessagesCollection.subscribeChanges((changes) => {
52
+ for (const change of changes) {
53
+ if (change.type === 'insert') {
54
+ controller.enqueue(JSON.stringify(change.value) + '\n')
55
+ }
56
+ }
57
+ })
58
+ },
59
+ })
60
+
61
+ return new Response(stream, {
62
+ headers: {
63
+ 'Content-Type': 'application/x-ndjson',
64
+ },
65
+ })
66
+ },
67
+ POST: async ({ request }) => {
68
+ const message = IncomingMessageSchema.safeParse(await request.json())
69
+ if (!message.success) {
70
+ return new Response(message.error.message, { status: 400 })
71
+ }
72
+ sendMessage(message.data)
73
+ },
74
+ })
@@ -0,0 +1,15 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+
3
+ import ChatArea from '@/components/demo.chat-area'
4
+
5
+ export const Route = createFileRoute('/demo/db-chat')({
6
+ component: App,
7
+ })
8
+
9
+ function App() {
10
+ return (
11
+ <div className="flex flex-col h-screen bg-gray-50">
12
+ <ChatArea />
13
+ </div>
14
+ )
15
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "DB",
3
+ "description": "TanStack DB",
4
+ "phase": "add-on",
5
+ "type": "add-on",
6
+ "modes": ["file-router"],
7
+ "link": "https://tanstack.com/db/latest",
8
+ "dependsOn": ["tanstack-query", "start"],
9
+ "routes": [
10
+ {
11
+ "url": "/demo/db-chat",
12
+ "name": "DB Chat",
13
+ "path": "src/routes/demo.db-chat.tsx",
14
+ "jsName": "DBChatDemo"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "dependencies": {
3
+ "@tanstack/db": "^0.1.1",
4
+ "@tanstack/query-db-collection": "^0.2.0",
5
+ "@tanstack/react-db": "^0.1.1",
6
+ "zod": "^4.0.14"
7
+ }
8
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 633 633"><defs><linearGradient id="b" x1="50%" x2="50%" y1="0%" y2="71.65%"><stop offset="0%" stop-color="#6BDAFF"/><stop offset="31.922%" stop-color="#F9FFB5"/><stop offset="70.627%" stop-color="#FFA770"/><stop offset="100%" stop-color="#FF7373"/></linearGradient><linearGradient id="d" x1="43.996%" x2="53.441%" y1="8.54%" y2="93.872%"><stop offset="0%" stop-color="#673800"/><stop offset="100%" stop-color="#B65E00"/></linearGradient><linearGradient id="e" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#2F8A00"/><stop offset="100%" stop-color="#90FF57"/></linearGradient><linearGradient id="f" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#2F8A00"/><stop offset="100%" stop-color="#90FF57"/></linearGradient><linearGradient id="g" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#2F8A00"/><stop offset="100%" stop-color="#90FF57"/></linearGradient><linearGradient id="h" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#2F8A00"/><stop offset="100%" stop-color="#90FF57"/></linearGradient><linearGradient id="i" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#2F8A00"/><stop offset="100%" stop-color="#90FF57"/></linearGradient><linearGradient id="j" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#2F8A00"/><stop offset="100%" stop-color="#90FF57"/></linearGradient><linearGradient id="k" x1="92.9%" x2="8.641%" y1="45.768%" y2="54.892%"><stop offset="0%" stop-color="#EE2700"/><stop offset="100%" stop-color="#FF008E"/></linearGradient><linearGradient id="l" x1="61.109%" x2="43.717%" y1="3.633%" y2="43.072%"><stop offset="0%" stop-color="#FFF400"/><stop offset="100%" stop-color="#3C8700"/></linearGradient><linearGradient id="m" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFDF00"/><stop offset="100%" stop-color="#FF9D00"/></linearGradient><linearGradient id="n" x1="127.279%" x2="0%" y1="49.778%" y2="50.222%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><linearGradient id="o" x1="127.279%" x2="0%" y1="47.531%" y2="52.469%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><linearGradient id="p" x1="127.279%" x2="0%" y1="46.195%" y2="53.805%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><linearGradient id="q" x1="127.279%" x2="0%" y1="35.33%" y2="64.67%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><linearGradient id="r" x1="127.279%" x2="0%" y1="4.875%" y2="95.125%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><linearGradient id="s" x1="78.334%" x2="31.668%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><linearGradient id="t" x1="57.913%" x2="44.88%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><linearGradient id="u" x1="50.495%" x2="49.68%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><circle id="a" cx="308.5" cy="308.5" r="308.5"/><circle id="v" cx="307.5" cy="308.5" r="316.5"/></defs><g fill="none" fill-rule="evenodd" transform="translate(9 8)"><mask id="c" fill="#fff"><use xlink:href="#a"/></mask><use xlink:href="#a" fill="url(#b)"/><ellipse cx="81.5" cy="602.5" fill="#015064" stroke="#00CFE2" stroke-width="25" mask="url(#c)" rx="214.5" ry="185.968"/><ellipse cx="535.5" cy="602.5" fill="#015064" stroke="#00CFE2" stroke-width="25" mask="url(#c)" rx="214.5" ry="185.968"/><ellipse cx="81.5" cy="640.5" fill="#015064" stroke="#00A8B8" stroke-width="25" mask="url(#c)" rx="214.5" ry="185.968"/><ellipse cx="535.5" cy="640.5" fill="#015064" stroke="#00A8B8" stroke-width="25" mask="url(#c)" rx="214.5" ry="185.968"/><ellipse cx="81.5" cy="676.5" fill="#015064" stroke="#007782" stroke-width="25" mask="url(#c)" rx="214.5" ry="185.968"/><ellipse cx="535.5" cy="676.5" fill="#015064" stroke="#007782" stroke-width="25" mask="url(#c)" rx="214.5" ry="185.968"/><g mask="url(#c)"><path fill="url(#d)" stroke="#6E3A00" stroke-width="6.088" d="M98.318 88.007c7.691 37.104 16.643 72.442 26.856 106.013 10.212 33.571 25.57 68.934 46.07 106.088l-51.903 11.67c-10.057-60.01-17.232-99.172-21.525-117.487-4.293-18.315-10.989-51.434-20.089-99.357l20.591-6.927" transform="scale(-1 1) rotate(25 -300.37 -553.013)"/><g stroke="#2F8A00"><path fill="url(#e)" stroke-width="9.343" d="M108.544 66.538s-13.54-21.305-37.417-27.785c-15.917-4.321-33.933.31-54.048 13.892C33.464 65.975 47.24 73.52 58.405 75.28c16.749 2.64 50.14-8.74 50.14-8.74Z" transform="rotate(1 -6061.691 5926.397)"/><path fill="url(#f)" stroke-width="9.343" d="M108.544 67.138s-47.187-5.997-81.077 19.936C4.873 104.362-3.782 137.794 1.502 187.369c28.42-29.22 48.758-50.836 61.016-64.846 18.387-21.016 46.026-55.385 46.026-55.385Z" transform="rotate(1 -6061.691 5926.397)"/><path fill="url(#g)" stroke-width="9.343" d="M108.544 66.538c-1.96-21.705 3.98-38.018 17.82-48.94C140.203 6.674 154.85.808 170.303 0c-4.865 21.527-12.373 36.314-22.524 44.361-10.151 8.048-23.23 15.44-39.236 22.177Z" transform="rotate(1 -6061.691 5926.397)"/><path fill="url(#h)" stroke-width="9.343" d="M108.544 67.138c29.838-31.486 61.061-42.776 93.669-33.869C234.82 42.176 253.749 60.785 259 89.096c-34.898-3.657-59.974-6.343-75.228-8.058-15.254-1.716-40.33-6.349-75.228-13.9Z" transform="rotate(1 -6061.691 5926.397)"/><path fill="url(#i)" stroke-width="9.343" d="M108.544 67.138c34.868-9.381 64.503-3.658 88.905 17.17 24.402 20.829 39.656 46.686 45.762 77.571-39.626-7.574-68.4-20.115-86.322-37.624a395.051 395.051 0 0 1-48.345-57.117Z" transform="rotate(1 -6061.691 5926.397)"/><path fill="url(#j)" stroke-width="9.343" d="M108.544 67.138C76.206 82.6 57.608 105.366 52.75 135.436c-4.858 30.07-.292 62.89 13.698 98.462 24.873-41.418 38.905-71.368 42.096-89.849 3.191-18.48 3.191-44.118 0-76.91Z" transform="rotate(1 -6061.691 5926.397)"/><path stroke-linecap="round" stroke-width="5.91" d="M211.284 173.477c-13.851 21.992-23.291 42.022-28.32 60.093-5.03 18.071-8.175 33.143-9.436 45.216"/><path stroke-linecap="round" stroke-width="5.91" d="M209.814 176.884c-23.982 2.565-42.792 10.469-56.428 23.714-13.639 13.245-23.483 26.136-29.536 38.674M219.045 167.299c29.028-7.723 50.972-10.173 65.831-7.352 14.859 2.822 26.807 7.659 35.842 14.51M211.31 172.618c20.29 9.106 38.353 19.052 54.186 29.837 15.833 10.786 27.714 20.99 35.643 30.617"/></g><path stroke="#830305" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="6.937" d="m409.379 398.157-23.176 18.556M328.04 375.516l-22.313 28.398M312.904 353.698l53.18 59.816"/><path fill="url(#k)" d="M67.585 27.463H5.68C1.893 28.148 0 30.38 0 34.16c0 3.78 1.893 6.211 5.68 7.293h67.17l41.751-30.356c2.488-2.646 2.794-5.315.92-8.006s-4.6-3.626-8.177-2.803l-39.76 27.174Z" transform="scale(-1 1) rotate(-9 2092.128 2856.854)"/><path fill="#D8D8D8" stroke="#FFF" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="4.414" d="m402.861 391.51.471-4.088M382.21 388.752l.472-4.087M361.546 385.404l.485-3.845M337.59 371.883l2.56-2.498M324.276 359.567l2.56-2.497"/></g><ellipse cx="308.5" cy="720.5" fill="url(#l)" mask="url(#c)" rx="266" ry="316.5"/><ellipse cx="308.5" cy="720.5" stroke="#6DA300" stroke-opacity=".502" stroke-width="26" mask="url(#c)" rx="253" ry="303.5"/><g mask="url(#c)"><g transform="translate(389 -32)"><circle cx="168.5" cy="113.5" r="113.5" fill="url(#m)"/><circle cx="168.5" cy="113.5" r="106" stroke="#FFC900" stroke-opacity=".529" stroke-width="15"/><path stroke="url(#n)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="M30 113H0"/><path stroke="url(#o)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="M33.5 79.5 7 74"/><path stroke="url(#p)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="m34 146-29 8"/><path stroke="url(#q)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="m45 177-24 13"/><path stroke="url(#r)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="m67 204-20 19"/><path stroke="url(#s)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="m94.373 227-13.834 22.847"/><path stroke="url(#t)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="M127.5 243.5 120 268"/><path stroke="url(#u)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="m167.5 252.5.5 24.5"/></g></g><circle cx="307.5" cy="308.5" r="304" stroke="#000" stroke-width="25"/></g></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/cta-framework-react-cra",
3
- "version": "0.17.0",
3
+ "version": "0.17.1",
4
4
  "description": "CTA Framework for React (Create React App)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -23,7 +23,7 @@
23
23
  "author": "Jack Herrington <jherr@pobox.com>",
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
- "@tanstack/cta-engine": "0.17.0"
26
+ "@tanstack/cta-engine": "0.17.1"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^22.13.4",