Compare commits

...

37 Commits

Author SHA1 Message Date
Anselm
f224b2b4ea Release 2024-10-01 10:11:26 +01:00
Anselm Eickhoff
f70d34fb0b Merge pull request #462 from gardencmp/gudorsi-jazz-173
changeset for the cursor fix
2024-10-01 10:09:49 +01:00
Guido D'Orsi
a075f90890 changeset 2024-10-01 11:08:36 +02:00
Anselm Eickhoff
66686e4f71 Merge pull request #461 from gardencmp/gudorsi-jazz-173
fix(jazz-react): fix cursor reset on text input updates
2024-10-01 10:07:34 +01:00
Anselm Eickhoff
e8a6f9d123 Merge pull request #460 from gardencmp/feature/text-cursor-position-test
test(CoMap): add a failing test to check the cursor position while editing text inputs
2024-10-01 10:06:44 +01:00
Trisha Lim
9246c009b8 Add example apps to docs (#456)
* Add example apps to docs
2024-10-01 09:58:25 +01:00
Guido D'Orsi
afa43dc248 fix(jazz-react): fix cursor reset on text input updates 2024-09-30 19:27:54 +02:00
Guido D'Orsi
effa15082e test(CoMap): add a failing test to check the cursor position while editing text inputs 2024-09-30 19:27:27 +02:00
Anselm Eickhoff
2a648a620c Merge pull request #459 from gardencmp/fix-tests
Fix tests
2024-09-30 14:52:52 +01:00
pax-k
f10d13be3b fix: tests 2024-09-30 16:49:31 +03:00
Anselm Eickhoff
e8f7e90220 Merge pull request #454 from gardencmp/trishalim-jazz-334
Add api-reference page listing packages and descriptions
2024-09-30 11:23:03 +01:00
Trisha Lim
6bf16fd52c Add api-reference page listing packages and descriptions 2024-09-27 14:41:54 +01:00
Anselm Eickhoff
f143a4aa4d Merge pull request #451 from gardencmp/trishalim-jazz-337
Set blue (primary) color palette
2024-09-26 10:50:09 -07:00
Trisha Lim
0c07fcee1c Set blue (primary) color palette 2024-09-26 18:01:06 +01:00
Anselm Eickhoff
eca9698bbc Merge pull request #448 from gardencmp/trishalim-jazz-320
Improve styling for code snippets
2024-09-25 08:46:40 -07:00
Trisha Lim
de66d90b85 Styling for removed line in code snippet 2024-09-25 16:16:55 +01:00
Trisha Lim
4bc815a576 Improve styling for code snippets 2024-09-25 14:30:19 +01:00
Anselm Eickhoff
0bd4fea0dd Merge pull request #444 from gardencmp/trishalim-jazz-307
Emphasize invite link to join chat in chat example
2024-09-24 13:01:57 -07:00
Trisha Lim
35c310dc47 Add "copy url to invite" button 2024-09-24 19:53:04 +01:00
Trisha Lim
f80442793a Switch to url field 2024-09-24 19:17:44 +01:00
Anselm Eickhoff
98be05f697 Merge pull request #441 from gardencmp/anselm-jazz-332
Finish Guide
2024-09-24 11:13:39 -07:00
Trisha Lim
0b6e9e6c4d Dark mode styling for homepage chat example 2024-09-24 18:59:49 +01:00
Trisha Lim
45046f571f Mobile view for homepage chat example 2024-09-24 18:36:11 +01:00
Trisha Lim
4ccad8ac0b Add invite link 2024-09-24 18:17:46 +01:00
Anselm
e4d68bb56b Heading structure for Groups & Permissions 2024-09-24 10:02:27 -07:00
Anselm
ab7c560fbb Merge branch 'main' into anselm-jazz-332 2024-09-24 09:48:55 -07:00
Anselm Eickhoff
32c820be56 Merge pull request #440 from gardencmp/trishalim-jazz-322
Redesign UI of docs side nav
2024-09-24 09:43:30 -07:00
Anselm
f5d7c9fd6b Fix details marker on Safari 2024-09-24 09:36:59 -07:00
Anselm
6b90c6048b New intro 2024-09-24 09:29:52 -07:00
Trisha Lim
80bb793e3a Scroll chat body only, not whole screen 2024-09-24 15:56:09 +01:00
Trisha Lim
b275ffbe01 Redesign code example section 2024-09-24 15:43:37 +01:00
Trisha Lim
49f60bda67 Minimal UI improvements to chat example 2024-09-24 15:19:03 +01:00
Trisha Lim
8b52f180af Get design system app running 2024-09-24 13:47:33 +01:00
Trisha Lim
46365a35fe Redesign UI of docs side nav 2024-09-24 13:46:53 +01:00
Anselm
ac9dab0b3b Group explanation and half of invitation flow 2024-09-23 12:30:56 -07:00
Anselm
9b1227915e Comment out TODOs 2024-09-23 11:34:38 -07:00
Anselm
dbb024e4da Reshuffle headings 2024-09-23 11:34:18 -07:00
76 changed files with 1625 additions and 728 deletions

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
project: ["e2e/BinaryCoStream", "examples/pets"]
project: ["e2e/BinaryCoStream", "e2e/CoValues", "examples/pets"]
steps:
- uses: actions/checkout@v3

View File

@@ -1,5 +1,13 @@
# @jazz-e2e/binarycostream
## 0.0.83
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
- jazz-react@0.8.2
## 0.0.82
### Patch Changes

View File

@@ -5,7 +5,7 @@
<link rel="icon" type="image/png" href="/jazz-logo.png" />
<link rel="stylesheet" href="/src/index.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Chat Example</title>
<title>Jazz BinaryCoStream Tests</title>
</head>
<body>
<div id="root"></div>

View File

@@ -1,7 +1,7 @@
{
"name": "@jazz-e2e/binarycostream",
"private": true,
"version": "0.0.82",
"version": "0.0.83",
"type": "module",
"scripts": {
"dev": "vite",
@@ -17,8 +17,8 @@
"cojson": "workspace:0.8.0",
"hash-slash": "workspace:0.2.0",
"is-ci": "^3.0.1",
"jazz-react": "workspace:0.8.1",
"jazz-tools": "workspace:0.8.1",
"jazz-react": "workspace:0.8.2",
"jazz-tools": "workspace:0.8.2",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},

30
e2e/CoValues/.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
sync-db/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@@ -0,0 +1,9 @@
# @jazz-e2e/covalues
## 0.0.82
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
- jazz-react@0.8.2

14
e2e/CoValues/index.html Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/jazz-logo.png" />
<link rel="stylesheet" href="/src/index.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz CoValues tests</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/app.tsx"></script>
</body>
</html>

37
e2e/CoValues/package.json Normal file
View File

@@ -0,0 +1,37 @@
{
"name": "@jazz-e2e/covalues",
"private": true,
"version": "0.0.82",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "playwright test",
"test:ui": "playwright test --ui"
},
"lint-staged": {
"*.{js,jsx,mdx,json}": "prettier --write"
},
"dependencies": {
"cojson": "workspace:*",
"hash-slash": "workspace:*",
"is-ci": "^3.0.1",
"jazz-react": "workspace:*",
"jazz-tools": "workspace:*",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0"
},
"devDependencies": {
"@playwright/test": "^1.46.1",
"@types/node": "^22.5.1",
"@types/react": "^18.2.19",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react-swc": "^3.3.2",
"jstat": "^1.9.6",
"typescript": "^5.3.3",
"vite": "^5.0.10"
}
}

View File

@@ -0,0 +1,49 @@
import { defineConfig, devices } from "@playwright/test";
import isCI from "is-ci";
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: isCI,
/* Retry on CI only */
retries: isCI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: isCI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: isCI ? "http://localhost:4173/" : "http://localhost:5173",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
/* Run your local dev server before starting the tests */
webServer: isCI ? {
command: "pnpm preview",
url: "http://localhost:4173/",
} : undefined,
});

25
e2e/CoValues/src/app.tsx Normal file
View File

@@ -0,0 +1,25 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { AuthAndJazz } from "./jazz";
import { TestInput } from "./pages/TestInput";
import { RouterProvider, createHashRouter } from "react-router-dom";
const router = createHashRouter([
{
path: "/",
element: <TestInput />,
},
{
path: "/test-input",
element: <TestInput />,
},
]);
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<AuthAndJazz>
<RouterProvider router={router} />
</AuthAndJazz>
</React.StrictMode>,
);

25
e2e/CoValues/src/jazz.tsx Normal file
View File

@@ -0,0 +1,25 @@
import { createJazzReactApp } from "jazz-react";
import { ephemeralCredentialsAuth } from "jazz-tools";
import { useState } from "react";
const key = `test-comap@jazz.tools`;
const localSync = new URLSearchParams(location.search).has("localSync");
const Jazz = createJazzReactApp();
export const { useAccount, useCoState } = Jazz;
export function AuthAndJazz({ children }: { children: React.ReactNode }) {
const [ephemeralAuth] = useState(ephemeralCredentialsAuth())
return (
<Jazz.Provider auth={ephemeralAuth} peer={
localSync
? `ws://localhost:4200?key=${key}`
: `wss://mesh.jazz.tools/?key=${key}`
}>
{children}
</Jazz.Provider>
);
}

View File

@@ -0,0 +1,35 @@
import { co, CoMap, Group, ID } from "jazz-tools";
import { useAccount, useCoState } from "../jazz";
import { useEffect, useState } from "react";
export class InputTestCoMap extends CoMap {
title = co.string;
}
export function TestInput() {
const [id, setId] = useState<ID<InputTestCoMap> | undefined>(undefined);
const coMap = useCoState(InputTestCoMap, id);
const { me } = useAccount();
useEffect(() => {
if (!me || id) return;
const group = Group.create({ owner: me });
group.addMember("everyone", "writer");
setId(InputTestCoMap.create({ title: "" }, { owner: group }).id);
}, [me]);
if (!coMap) return null;
return (
<input
value={coMap?.title ?? ""}
onChange={(e) => {
if (!coMap) return;
coMap.title = e.target.value;
}}
/>
);
}

1
e2e/CoValues/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -0,0 +1,14 @@
import { test, expect } from "@playwright/test";
test.describe("CoMap - TestInput", () => {
test("should keep the cursor position when typing", async ({ page }) => {
await page.goto("/test-input");
await page.getByRole("textbox").fill("xx");
await page.getByRole('textbox').press('ArrowLeft');
await page.getByRole("textbox").press("y");
await page.getByRole("textbox").press("y");
await expect(page.getByRole('textbox')).toHaveValue("xyyx");
});
});

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": "."
},
"include": ["src"],
}

View File

@@ -0,0 +1,10 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
build: {
minify: false
}
})

View File

@@ -1,5 +1,14 @@
# jazz-example-chat
## 0.0.82
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
- jazz-react@0.8.2
- jazz-react-auth-clerk@0.8.2
## 0.0.81
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat-clerk",
"private": true,
"version": "0.0.81",
"version": "0.0.82",
"type": "module",
"scripts": {
"dev": "vite",
@@ -23,9 +23,9 @@
"clsx": "^2.0.0",
"cojson": "workspace:0.8.0",
"hash-slash": "workspace:0.2.0",
"jazz-react": "workspace:0.8.1",
"jazz-react-auth-clerk": "workspace:0.8.1",
"jazz-tools": "workspace:0.8.1",
"jazz-react": "workspace:0.8.2",
"jazz-react-auth-clerk": "workspace:0.8.2",
"jazz-tools": "workspace:0.8.2",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -1,5 +1,13 @@
# jazz-example-chat
## 0.0.84
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
- jazz-react@0.8.2
## 0.0.83
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.83",
"version": "0.0.84",
"type": "module",
"scripts": {
"dev": "vite",
@@ -22,8 +22,8 @@
"clsx": "^2.0.0",
"cojson": "workspace:0.8.0",
"hash-slash": "workspace:0.2.0",
"jazz-react": "workspace:0.8.1",
"jazz-tools": "workspace:0.8.1",
"jazz-react": "workspace:0.8.2",
"jazz-tools": "workspace:0.8.2",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -19,7 +19,7 @@ export function App() {
return (
<AppContainer>
<TopBar>
{me?.profile?.name} · <button onClick={logOut}>Log out</button>
<p>{me?.profile?.name}</p> · <button onClick={logOut}>Log out</button>
</TopBar>
{useIframeHashRouter().route({
"/": () => createChat() as never,

View File

@@ -5,7 +5,7 @@ import {
BubbleBody,
BubbleContainer,
BubbleInfo,
ChatContainer,
ChatBody,
ChatInput,
EmptyChatMessage,
} from "./ui.tsx";
@@ -14,18 +14,20 @@ export function ChatScreen(props: { chatID: ID<Chat> }) {
const chat = useCoState(Chat, props.chatID, [{}]);
return chat ? (
<ChatContainer>
{chat.length > 0 ? (
chat.map(msg => <ChatBubble msg={msg} key={msg.id} />)
) : (
<EmptyChatMessage />
)}
<>
<ChatBody>
{chat.length > 0 ? (
chat.map(msg => <ChatBubble msg={msg} key={msg.id} />)
) : (
<EmptyChatMessage />
)}
</ChatBody>
<ChatInput
onSubmit={text => {
chat.push(Message.create({ text }, { owner: chat._owner }));
}}
/>
</ChatContainer>
</>
) : (
<div>Loading...</div>
);

View File

@@ -9,8 +9,6 @@ export const { useAccount, useCoState } = Jazz;
function JazzAndAuth({ children }: { children: React.ReactNode }) {
const [auth, state] = useDemoAuth();
console.log(state, auth)
return (
<>
<Jazz.Provider

View File

@@ -1,6 +1,6 @@
export function AppContainer(props: { children: React.ReactNode }) {
return (
<div className="flex flex-col items-center justify-between w-screen h-screen p-2 dark:bg-black dark:text-white">
<div className="flex flex-col justify-between w-screen h-screen bg-stone-50 dark:bg-black dark:text-white">
{props.children}
</div>
);
@@ -8,20 +8,22 @@ export function AppContainer(props: { children: React.ReactNode }) {
export function TopBar(props: { children: React.ReactNode }) {
return (
<div className="mb-5 px-2 py-1 text-sm self-end">{props.children}</div>
<div className="p-3 bg-white w-full flex justify-end gap-1 text-xs border-b dark:bg-transparent dark:border-stone-800">
{props.children}
</div>
);
}
export function ChatContainer(props: { children: React.ReactNode }) {
export function ChatBody(props: { children: React.ReactNode }) {
return (
<div className="w-full max-w-xl h-full flex flex-col items-stretch">
<div className="flex-1 overflow-y-auto">
{props.children}
</div>
);
}
export function EmptyChatMessage() {
return <div className="m-auto text-sm">(Empty chat)</div>;
return <div className="h-full text-base text-stone-500 flex items-center justify-center px-3 md:text-xl">Start a conversation below.</div>;
}
export function BubbleContainer(props: {
@@ -29,12 +31,12 @@ export function BubbleContainer(props: {
fromMe: boolean | undefined;
}) {
const align = props.fromMe ? "items-end" : "items-start";
return <div className={`${align} flex flex-col`}>{props.children}</div>;
return <div className={`${align} flex flex-col m-2`}>{props.children}</div>;
}
export function BubbleBody(props: { children: React.ReactNode }) {
return (
<div className="rounded-xl bg-stone-100 dark:bg-stone-700 dark:text-white py-2 px-4 mt-2 min-w-[5rem]">
<div className="rounded-2xl text-sm bg-white dark:bg-stone-700 dark:text-white py-1 px-3 shadow-sm">
{props.children}
</div>
);
@@ -42,22 +44,24 @@ export function BubbleBody(props: { children: React.ReactNode }) {
export function BubbleInfo(props: { by: string | undefined; madeAt: Date }) {
return (
<div className="text-xs text-neutral-500 ml-2">
{props.by} {props.madeAt.toLocaleTimeString()}
<div className="text-xs text-neutral-500 mt-1.5">
{props.by} · {props.madeAt.toLocaleTimeString()}
</div>
);
}
export function ChatInput(props: { onSubmit: (text: string) => void }) {
return (
<input
className="rounded p-2 border mt-auto dark:bg-black dark:text-white border-stone-300 dark:border-stone-700"
placeholder="Type a message and press Enter"
onKeyDown={({ key, currentTarget: input }) => {
if (key !== "Enter" || !input.value) return;
props.onSubmit(input.value);
input.value = "";
}}
/>
<div className="p-3 bg-white border-t shadow-2xl mt-auto dark:bg-transparent dark:border-stone-800">
<input
className="rounded-full py-2 px-4 text-sm border block w-full dark:bg-black dark:text-white dark:border-stone-700"
placeholder="Type a message and press Enter"
onKeyDown={({ key, currentTarget: input }) => {
if (key !== "Enter" || !input.value) return;
props.onSubmit(input.value);
input.value = "";
}}
/>
</div>
);
}

View File

@@ -1,5 +1,13 @@
# jazz-example-musicplayer
## 0.0.5
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
- jazz-react@0.8.2
## 0.0.4
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-music-player",
"private": true,
"version": "0.0.4",
"version": "0.0.5",
"type": "module",
"scripts": {
"dev": "vite",
@@ -17,8 +17,8 @@
"dependencies": {
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-react": "workspace:0.8.1",
"jazz-tools": "workspace:0.8.1",
"jazz-react": "workspace:0.8.2",
"jazz-tools": "workspace:0.8.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.16.0",

View File

@@ -1,5 +1,13 @@
# jazz-password-manager
## 0.0.4
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
- jazz-react@0.8.2
## 0.0.3
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-password-manager",
"private": true,
"version": "0.0.3",
"version": "0.0.4",
"type": "module",
"scripts": {
"dev": "vite",
@@ -11,8 +11,8 @@
"clean-install": "rm -rf node_modules pnpm-lock.yaml && pnpm install"
},
"dependencies": {
"jazz-react": "workspace:0.8.1",
"jazz-tools": "workspace:0.8.1",
"jazz-react": "workspace:0.8.2",
"jazz-tools": "workspace:0.8.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.41.5",

View File

@@ -1,5 +1,14 @@
# jazz-example-pets
## 0.0.102
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
- jazz-browser-media-images@0.8.2
- jazz-react@0.8.2
## 0.0.101
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.101",
"version": "0.0.102",
"type": "module",
"scripts": {
"dev": "vite",
@@ -22,9 +22,9 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-browser-media-images": "workspace:0.8.1",
"jazz-react": "workspace:0.8.1",
"jazz-tools": "workspace:0.8.1",
"jazz-browser-media-images": "workspace:0.8.2",
"jazz-react": "workspace:0.8.2",
"jazz-tools": "workspace:0.8.2",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -1,5 +1,13 @@
# jazz-example-todo
## 0.0.101
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
- jazz-react@0.8.2
## 0.0.100
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.100",
"version": "0.0.101",
"type": "module",
"scripts": {
"dev": "vite",
@@ -20,8 +20,8 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-react": "workspace:0.8.1",
"jazz-tools": "workspace:0.8.1",
"jazz-react": "workspace:0.8.2",
"jazz-tools": "workspace:0.8.2",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -6,16 +6,16 @@ export function Prose(props: { children: ReactNode; className?: string }) {
<div
className={clsx(
"mb-4",
"max-w-none prose-stone dark:prose-invert",
"max-w-4xl prose-stone dark:prose-invert",
"prose-headings:font-display",
"prose-h1:text-4xl lg:prose-h1:text-5xl prose-h1:font-medium prose-h1:tracking-tight",
"prose-h2:text-2xl lg:prose-h2:text-3xl prose-h2:font-medium prose-h2:tracking-tight",
"prose-p:max-w-3xl prose-p:leading-snug",
"lg:prose-h1:text-5xl prose-h1:font-medium prose-h1:tracking-tight",
"lg:prose-h2:text-3xl prose-h2:font-medium prose-h2:tracking-tight",
"prose-p:leading-snug",
"prose-strong:font-medium",
"prose-code:font-normal prose-code:leading-tight prose-code:before:content-none prose-code:after:content-none prose-code:bg-stone-100 prose-code:dark:bg-stone-900 prose-code:p-1 prose-code:rounded",
"prose-pre:text-black dark:prose-pre:text-white prose-pre:max-w-3xl prose-pre:text-[0.8em] prose-pre:leading-[1.3] prose-pre:-mt-2 prose-pre:my-4 prose-pre:px-10 prose-pre:py-2 prose-pre:bg-transparent",
"[&_pre_.line]:relative [&_pre_.line]:min-h-[1.3em] [&_pre_.lineNo]:text-[0.75em] [&_pre_.lineNo]:text-stone-300 [&_pre_.lineNo]:dark:text-stone-700 [&_pre_.lineNo]:absolute [&_pre_.lineNo]:text-right [&_pre_.lineNo]:w-8 [&_pre_.lineNo]:-left-10 [&_pre_.lineNo]:top-[0.3em] [&_pre_.lineNo]:select-none",
props.className || "prose lg:prose-lg"
"prose-code:font-normal prose-code:before:content-none prose-code:after:content-none prose-code:bg-stone-100 prose-code:dark:bg-stone-900 prose-code:p-1 prose-code:rounded",
"prose-pre:border prose-pre:p-0 prose-pre:bg-white prose-pre:dark:bg-stone-900 dark:prose-pre:border-stone-800",
"prose-pre:text-black dark:prose-pre:text-white",
props.className || "prose"
)}
>
{props.children}

View File

@@ -104,7 +104,7 @@ export function Nav({
<nav
className={clsx(
"md:hidden fixed flex flex-col bottom-4 right-4 z-50",
"bg-stone-50 dark:bg-stone-925 dark:text-white border border-stone-100 dark:border-stone-900 dark:outline dark:outline-1 dark:outline-black/60 rounded-lg shadow-lg",
"bg-stone-50 dark:bg-stone-925 border border-stone-100 dark:border-stone-900 dark:outline dark:outline-1 dark:outline-black/60 rounded-lg shadow-lg",
menuOpen || searchOpen ? "left-4" : "",
)}
>
@@ -133,8 +133,8 @@ export function Nav({
))}
</div>
{pathname === "/docs" && (
<div className="max-h-[calc(100dvh-15rem)] p-4 border-b border-stone-100 dark:border-stone-900 overflow-x-auto prose-sm prose-ul:pl-1 prose-ul:ml-1 prose-li:my-2 prose-li:leading-tight prose-ul:list-['-']">
{pathname.startsWith("/docs") && (
<div className="max-h-[calc(100dvh-15rem)] p-4 border-b border-stone-100 dark:border-stone-900 overflow-x-auto">
{docNav}
</div>
)}
@@ -295,4 +295,4 @@ function NavLinkLogo({
{children}
</Link>
);
}
}

View File

@@ -15,9 +15,22 @@ const inter = Inter({
variable: "--font-inter",
display: "swap",
});
const pragmata = localFont({
src: "../../fonts/ppr_0829.woff2",
variable: "--font-ppr",
const commitMono = localFont({
src: [
{
path: "../../fonts/CommitMono-Regular.woff2",
weight: "400",
style: "normal",
},
{
path: "../../fonts/CommitMono-Regular.woff",
weight: "400",
style: "normal",
},
],
variable: "--font-commit-mono",
display: "swap",
});
export const metadata: Metadata = {
@@ -31,12 +44,13 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={[
manrope.variable,
pragmata.variable,
inter.className,
].join(" ")}>{children}</body>
<html lang="en" className="h-full">
<body className={[
manrope.variable,
commitMono.variable,
inter.className,
"h-full",
].join(" ")}>{children}</body>
</html>
);
}

View File

@@ -1,42 +1,7 @@
import Image from 'next/image';
import { Hero } from './components/organisms/Hero';
import { Headline } from './components/molecules/Headline';
import { Title } from './components/atoms/Title';
import { Body } from './components/atoms/Body';
import { Card } from './components/molecules/Card';
import { CardSet } from './components/organisms/CardSet';
const catIpsumLong = 'Shake treat bag. Vomit food and eat it again all of a sudden cat goes crazy murr i hate humans they are so annoying where is it? i saw that bird i need to bring it home to mommy squirrel! hack up furballs. Meeeeouw walk on keyboard and slap the dog because cats rule, and behind the couch. Catto munch salmono love the best thing in the universe is a cardboard box or favor packaging over toy cat ass trophy. I vomit in the bed in the middle of the night chase laser. Eat and than sleep on your face play riveting piece on synthesizer keyboard reward the chosen human with a slow blink plan your travel.';
const catIpsumShort = <>Tuxedo cats <strong>always</strong> looking dapper chew iPad power cord, get away from me stupid dog for thinking about you i'm joking it's food always food so roll on the floor purring your whiskers off</>;
const catIpsumHeadline = <>Need to check on <em>human</em>, have not seen in an hour might be dead</>;
const card = {
cardTitle: "Cats are awesome",
text: catIpsumShort,
}
const cardSet = {
card1: card,
card2: card,
card3: card,
}
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center px-24">
<Hero title="Title" subheading="Subheading" />
<Headline headline="Headline" text={catIpsumHeadline} />
<Title text="Title" />
{/* <Label text="text" /> */}
<Body text={catIpsumLong}/>
{/* <Icon text="text" /> */}
{/* <Button text="text" /> */}
<Card cardTitle="Cats are awesome" text={catIpsumShort} />
<Body text={catIpsumShort}/>
<CardSet cardSet={cardSet} />
<main className="container h-full flex flex-col gap-8 py-8 lg:py-16">
<h1 className="text-2xl font-semibold font-display">Jazz Design System</h1>
</main>
);
}

View File

@@ -1,6 +1,7 @@
import harmonyPalette from "@evilmartians/harmony/tailwind";
import typography from "@tailwindcss/typography";
import tailwindCSSAnimate from "tailwindcss-animate";
const colors = require("tailwindcss/colors")
/** @type {import('tailwindcss').Config} */
const config = {
@@ -28,8 +29,12 @@ const config = {
"950": "oklch(0.193359 0.002 75 / <alpha-value>)",
},
blue: {
...harmonyPalette.blue,
...colors.indigo,
"500": "#5870F1",
"600": "#3651E7",
"700": "#3313F7",
"800": "#2A12BE",
"900": "#12046A",
DEFAULT: "#3313F7",
},
},
@@ -93,23 +98,23 @@ const config = {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
container: {
center: true,
padding: {
DEFAULT: "0.75rem",
sm: "1rem",
lg: "2rem",
container: {
center: true,
padding: {
DEFAULT: "0.75rem",
sm: "1rem",
lg: "2rem",
},
screens: {
md: "960px",
lg: "1280px",
},
},
screens: {
md: "960px",
lg: "1280px",
},
},
screens: {
md: "960px",
lg: "1280px",
},
},
plugins: [tailwindCSSAnimate, typography()],
};

View File

@@ -110,7 +110,7 @@ export default function Home() {
slogan="A chat app in 174 lines of code."
/>
<GappedGrid className="mt-0">
<div className="flex flex-col md:grid md:grid-cols-2 md:divide-x border rounded-sm overflow-hidden shadow-sm dark:border-stone-900 dark:divide-stone-900">
<CodeExampleTabs
tabs={[
{
@@ -134,14 +134,19 @@ export default function Home() {
content: <Ui_tsx />,
},
]}
className="col-span-full md:col-span-4"
/>
<ResponsiveIframe
src="https://chat.jazz.tools"
localSrc="http://localhost:5173"
className="order-first col-span-full lg:col-span-2 rounded-sm overflow-hidden min-h-[50vh] lg:order-last"
/>
</GappedGrid>
<div className="border-b order-first md:order-last flex flex-col md:border-b-0">
<div className="flex border-b overflow-x-auto overflow-y-hidden bg-white dark:border-stone-900 dark:bg-stone-900">
<p className="flex items-center -mb-px transition-colors px-3 pb-1.5 pt-2 block text-xs border-b-2 border-blue-700 text-stone-700 dark:bg-stone-925 dark:text-blue-500 dark:border-blue-500">
result
</p>
</div>
<ResponsiveIframe
src="https://chat.jazz.tools"
localSrc="http://localhost:5173"
/>
</div>
</div>
</div>
<div>

View File

@@ -7,7 +7,7 @@ interface Props {
}
export default function Page({ params }: Props) {
if (!packages.includes(params.package)) {
if (!packages.map((p) => p.name).includes(params.package)) {
return notFound();
}
@@ -24,11 +24,5 @@ export async function generateMetadata({ params }: Props) {
export async function generateStaticParams() {
// TODO: ideally we check which files exist in ../../typedoc
return [
"jazz-tools",
"jazz-react",
"jazz-nodejs",
"jazz-browser",
"jazz-browser-media-images",
].map((pkg) => ({ package: pkg }));
return packages.map((pkg) => ({ package: pkg.name }));
}

View File

@@ -0,0 +1,104 @@
import { packages } from "@/lib/packages";
import { MessageCircleQuestionIcon, PackageIcon } from "lucide-react";
import Link from "next/link";
import { clsx } from "clsx";
const CardHeading = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => {
return (
<h2
className={clsx(
className,
"font-medium text-stone-950 dark:text-white text-lg transition-colors",
)}
>
{children}
</h2>
);
};
const CardBody = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => {
return <p className={clsx(className, "text-sm")}>{children}</p>;
};
const Card = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => {
return (
<div
className={clsx(
className,
"not-prose p-4 h-full rounded-xl flex flex-col gap-1.5 group lg:p-5",
)}
>
{children}
</div>
);
};
export default function Page() {
return (
<>
<h1>API Reference</h1>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
{packages.map(({ name, description }) => (
<Link
className="not-prose block"
href={`/docs/api-reference/${name}`}
key={name}
>
<Card className="border border-stone-200 dark:border-stone-900 shadow-sm">
<PackageIcon
size={25}
strokeWidth={1.5}
className="text-stone-500 dark:text-stone-400"
/>
<CardHeading className="group-hover:text-blue dark:group-hover:text-blue-600">
{name}
</CardHeading>
<CardBody>{description}</CardBody>
</Card>
</Link>
))}
<Card className="bg-stone-50 dark:bg-stone-925">
<MessageCircleQuestionIcon
size={25}
strokeWidth={1.5}
className="text-stone-500 dark:text-stone-400"
/>
<CardHeading>
Can&apos;t find what you&apos;re looking for?
</CardHeading>
<CardBody>
Get help from our{" "}
<Link href="https://discord.gg/utDMjHYg42">
Discord
</Link>
, or open an issue on{" "}
<Link href="https://github.com/gardencmp/jazz">
GitHub
</Link>
.
</CardBody>
</Card>
</div>
</>
);
}

View File

@@ -10,9 +10,17 @@ import { ComingSoonBadge } from 'gcmp-design-system/src/app/components/atoms/Com
/>
</div>
Our issues app will be quite simple, but it will have team collaboration. <span className="text-nowrap">**Let's call it... &ldquo;Circular.&rdquo;**</span>
## About this guide
We'll build everything **step-by-step,** in typical, immediately usable stages. We'll explore many important things Jazz does &mdash; so **follow along** or **just pick things out.**
You might notice that right now, this guide is the only form of documentation there is for Jazz.
Over time, we're hoping to introduce independent doc sections for every concept in Jazz, but right now this works as:
- a quickstart guide
- a reference for the concepts in Jazz (ordered from simple & most important to more advanced)
- a tutorial that makes you build a full app (that you can use as a base)
Plus, if you get stuck or you have questions, [ask us on Discord](https://discord.gg/utDMjHYg42)
and we'll know exactly where you're at.
<h2 id="guide-setup">Project Setup</h2>
@@ -86,9 +94,9 @@ We'll build everything **step-by-step,** in typical, immediately usable stages.
); // old
```
This sets Jazz up, extracts app-specific hooks for later, and wraps our app in the provider.
This sets Jazz up, extracts app-specific hooks for later, and wraps our app in the provider.
TODO: explain Auth
{/* TODO: explain Auth */}
<h2 id="intro-to-covalues">Intro to CoValues</h2>
@@ -122,7 +130,7 @@ export class Issue extends CoMap {
}
```
TODO: explain what's happening
{/* TODO: explain what's happening */}
<h3 id="reading-covalues">Reading from CoValues</h3>
@@ -451,7 +459,11 @@ To see that sync is also already working, try the following:
- copy the URL to a new tab in the same browser window and see the same issue
- edit the issue and see the changes reflected in the other tab!
This works because we load the issue as the same account that created it and owns it (remember setting `{ owner: me }`?).
This works because we load the issue as the same account that created it and owns it (remember how you set `{ owner: me }`).
But how can we share an Issue with someone else?
<h3 id="simple-public-sharing">Simple Public Sharing</h3>
We'll learn more about access control in "Groups & Permissions", but for now let's build a super simple way of sharing an Issue by just making it publicly readable & writable.
@@ -504,9 +516,6 @@ export default App; // old
This concludes our intro to the essence of CoValues. Hopefully you're starting to have a feeling for how CoValues behave and how they're magically available everywhere.
<h3 id="simple-public-sharing">Simple Public Sharing</h3>
<ComingSoonBadge/>
<h2 id="refs-and-on-demand-subscribe">Refs & Auto-Subscribe</h2>
Now let's have a look at how to compose CoValues into more complex structures and build a whole app around them.
@@ -636,6 +645,8 @@ Two things to note here:
- We only need to use `useCoState` on the Project, and the nested `ListOfIssues` and each `Issue` will be **automatically loaded and subscribed to when we access them.**
- However, because either the `Project`, `ListOfIssues`, or each `Issue` might not be loaded yet, we have to check for them being defined.
<h3 id="loading-depth">Precise Loading Depths</h3>
The load-and-subscribe-on-access is a convenient way to have your rendering drive data loading (including in nested components!) and lets you quickly chuck UIs together without worrying too much about the shape of all data you'll need.
But you can also take more precise control over loading by defining a minimum-depth to load in `useCoState`:
@@ -680,8 +691,123 @@ The loading-depth spec `{ issues: [{}] }` means "in `Project`, load `issues` and
- Now, we can get rid of a lot of coniditional accesses because we know that once `project` is loaded, `project.issues` and each `Issue` in it will be loaded as well.
- This also results in only one rerender and visual update when everything is loaded, which is faster (especially for long lists) and gives you more control over the loading UX.
TODO: explain about not loaded vs not set/defined and `_refs` basics
{/* TODO: explain about not loaded vs not set/defined and `_refs` basics */}
<h2 id="groups-and-permissions">Groups & Permissions</h2>
We've seen briefly how we can use Groups to give everyone access to a Project,
and how we can use `{ owner: me }` to make something private to the current user.
<h3 id="groups-accounts-as-scopes">Groups / Accounts as Permission Scopes</h3>
This gives us a hint of how permissions work in Jazz: **every CoValue has an owner,
and the access rights on that CoValue are determined by its owner.**
- If the owner is an Account, only that Account can read and write the CoValue.
- If the owner is a Group, the access rights depend on the *role* of the Account (that is trying to access the CoValue) in that Group.
- `"reader"`s can read but not write to CoValues belonging to the Group.
- `"writer"`s can read and write to CoValues belonging to the Group.
- `"admin"`s can read and write to CoValues belonging to the Group *and can add and remove other members from the Group itself.*
<h3 id="creating-invites">Creating Invites</h3>
There is also an abstraction for creating *invitations to join a Group* (with a specific role) that you can use
to add people without having to know their Account ID.
Let's use these abstractions to build teams for a Project that we can invite people to.
Turns out, we're already mostly there! First, let's remove making the Project public:
{/* prettier-ignore */}
```tsx
import { useState } from "react"; // old
import { Project, ListOfIssues } from "./schema"; // old
import { ProjectComponent } from "./components/Project.tsx"; // old
import { useAccount } from "./main"; // old
import { ID, Group } from "jazz-tools" // old
// old
function App() { // old
const { me } = useAccount(); // old
const [projectID, setProjectID] = useState<ID<Project> | undefined>( // old
(window.location.search?.replace("?project=", "") || undefined) as ID<Project> | undefined, // old
); // old
// old
const createProject = () => { // old
const group = Group.create({ owner: me }); // old
group.addMember("everyone", "writer"); // *bin*
// old
const newProject = Project.create( // old
{ // old
name: "New Project", // old
issues: ListOfIssues.create([], { owner: group }) // old
}, // old
{ owner: group }, // old
); // old
setProjectID(newProject.id); // old
window.history.pushState({}, "", `?project=${newProject.id}`); // old
}; // old
// old
if (projectID) { // old
return <ProjectComponent projectID={projectID} />; // old
} else { // old
return <button onClick={createProject}>Create Project</button>; // old
} // old
} // old
// old
export default App; // old
```
Now, inside ProjectComponent, let's add a button to invite guests (read-only) or members (read-write) to the Project.
{/* prettier-ignore */}
```tsx
import { ID } from "jazz-tools"; // old
import { Project, Issue } from "../schema"; // old
import { IssueComponent } from "./Issue.tsx"; // old
import { useCoState } from "../main"; // old
import { createInviteLink } from "jazz-react";
// old
export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {// old
const project = useCoState(Project, projectID, { issues: [{}] }); // old
const invite = (role: "reader" | "writer") => {
const link = createInviteLink(project, role, { valueHint: "project" });
navigator.clipboard.writeText(link);
};
const createAndAddIssue = () => {// old
project?.issues.push(Issue.create({ // old
title: "",// old
description: "",// old
estimate: 0,// old
status: "backlog",// old
}, { owner: project._owner }));// old
};// old
// old
return project ? (// old
<div>// old
<h1>{project.name}</h1>// old
{project._owner?.myRole() === "admin" && (
<>
<button onClick={() => invite("reader")}>Invite Guest</button>
<button onClick={() => invite("writer")}>Invite Member</button>
</>
)}
<div className="border-r border-b">// old
{project.issues.map((issue) => ( // old
<IssueComponent key={issue.id} issue={issue} /> // old
))}// old
<button onClick={createAndAddIssue}>Create Issue</button>// old
</div>// old
</div>// old
) : (// old
<div>Loading project...</div>// old
);// old
}// old
```
<h3 id="consuming-invites">Consuming Invites</h3>
<div className="text-amber-500 mt-52">
🚧 OH NO - This is as far as we've written the Guide. 🚧
</div>

View File

@@ -14,17 +14,13 @@ export default function DocsLayout({
}) {
return (
<div className="relative grid grid-cols-12 gap-x-8 py-8">
<div
<DocNav
className={cn(
"md:col-span-4 lg:col-span-3",
"sticky align-start top-[4.75rem] h-[calc(100vh-8rem)] overflow-y-auto overflow-x-hidden",
"bg-stone-100 dark:bg-stone-900 text-stone-700 dark:text-stone-300 p-4 rounded-xl",
"hidden md:block",
"prose-sm prose-ul:pl-1 prose-ul:ml-1 prose-li:my-2 prose-li:leading-tight prose-ul:list-['-']",
)}
>
<DocNav />
</div>
/>
<div className="col-span-12 md:col-span-8 lg:col-span-9">
<Prose>{children}</Prose>
</div>

View File

@@ -0,0 +1,40 @@
export default function Page() {
const examples = [
{
name: "Chat",
slug: "chat",
},
{
name: "Music player",
slug: "music-player",
},
{
name: "Pets",
slug: "pets",
},
{
name: "Todo",
slug: "todo",
},
{
name: "Password manager",
slug: "password-manager",
},
];
return (
<>
<h1>Example Apps</h1>
<ul>
{examples.map(({ name, slug }) => (
<li key={name}>
<a
href={`https://github.com/gardencmp/jazz/tree/main/examples/${slug}`}
>
{name}
</a>
</li>
))}
</ul>
</>
);
}

View File

@@ -2,8 +2,6 @@
@tailwind components;
@tailwind utilities;
@layer base, shiki;
@layer base {
:root {
--background: 0 0% 100%;
@@ -66,350 +64,16 @@
--input: 12 6.5% 15.1%;
--ring: 24 5.7% 82.9%;
}
}
@layer base {
* {
@apply border-border;
}
.overlay-close {
background-color: "black";
@apply bg-black;
}
}
pre.shiki {
overflow: hidden;
}
pre.shiki:hover .dim {
opacity: 1;
}
pre.shiki div.dim {
opacity: 0.5;
}
pre.shiki div.dim,
pre.shiki div.highlight {
margin: 0;
padding: 0;
}
pre.shiki div.highlight {
opacity: 1;
background-color: #f1f8ff;
}
pre.shiki div.line {
/* min-height: 1rem; */
counter-increment: lineNumber 1;
}
pre.shiki div.line::before {
content: counter(lineNumber);
display: inline-block;
vertical-align: middle;
width: 1.3rem;
padding-right: 0.3rem;
text-align: right;
transition: color 0.3s;
@apply text-stone-200/50 dark:text-stone-900 text-[0.65rem];
}
pre.shiki div.line:hover::before {
@apply text-stone-400 dark:text-stone-600;
}
/** Don't show the language identifiers */
pre.shiki .language-id {
display: none;
}
/** When you mouse over the pre, show the underlines */
pre.twoslash:hover data-lsp {
@apply border-dotted border-b border-stone-300 dark:border-stone-700;
}
/** The tooltip-like which provides the LSP response */
pre.twoslash data-lsp::before {
content: attr(lsp);
position: absolute;
transform: translate(0, 1.2rem);
max-width: 30rem;
@apply text-xs px-1.5 py-1 rounded border border-stone-200 dark:border-stone-900 shadow-lg overflow-hidden whitespace-pre-wrap text-stone-700 bg-stone-50 dark:text-stone-200 dark:bg-stone-950;
text-align: left;
z-index: 100;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
display: none;
}
pre.twoslash data-lsp:hover::before {
display: block;
opacity: 1;
pointer-events: visible;
width: auto;
height: auto;
}
.shiki-outer {
@apply shadow-sm rounded-xl;
}
.shiki-filename {
@apply px-3 py-2 bg-stone-100 text-stone-700 dark:bg-stone-900 dark:text-stone-300 rounded-t-xl text-xs;
}
pre .code-container {
@apply overflow-auto p-2 pl-0 bg-white dark:bg-stone-925 rounded-b-xl text-xs dark:border-stone-900;
}
/* The try button */
pre .code-container > a {
position: absolute;
right: 8px;
bottom: 8px;
border-radius: 4px;
border: 1px solid #719af4;
padding: 0 8px;
color: #719af4;
text-decoration: none;
opacity: 0;
transition-timing-function: ease;
transition: opacity 0.3s;
}
/* Respect no animations */
@media (prefers-reduced-motion: reduce) {
pre .code-container > a {
transition: none;
}
}
pre .code-container > a:hover {
color: white;
background-color: #719af4;
}
pre .code-container:hover a {
opacity: 1;
}
pre code {
white-space: pre;
}
pre code a {
text-decoration: none;
}
pre data-err {
/* Extracted from VS Code */
background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E")
repeat-x bottom left;
padding-bottom: 3px;
}
pre .query {
margin-bottom: 10px;
color: #137998;
display: inline-block;
}
/* In order to have the 'popped out' style design and to not break the layout
/* we need to place a fake and un-selectable copy of the error which _isn't_ broken out
/* behind the actual error message.
/* This sections keeps both of those two in in sync */
pre .error,
pre .error-behind {
margin-left: -14px;
margin-top: 8px;
margin-bottom: 4px;
padding: 6px;
padding-left: 14px;
width: calc(100% - 20px);
white-space: pre-wrap;
display: block;
}
pre .error {
position: absolute;
background-color: #fee;
border-left: 2px solid #bf1818;
/* Give the space to the error code */
display: flex;
align-items: center;
color: black;
}
pre .error .code {
display: none;
}
pre .error-behind {
user-select: none;
visibility: transparent;
color: #fee;
}
/* Queries */
pre .arrow {
/* Transparent background */
background-color: #eee;
position: relative;
top: -7px;
margin-left: 0.1rem;
/* Edges */
border-left: 1px solid #eee;
border-top: 1px solid #eee;
transform: translateY(25%) rotate(45deg);
/* Size */
height: 8px;
width: 8px;
}
pre .popover {
margin-bottom: 10px;
background-color: #eee;
display: inline-block;
padding: 0 0.5rem 0.3rem;
margin-top: 10px;
border-radius: 3px;
}
/* Completion */
pre .inline-completions ul.dropdown {
display: inline-block;
position: absolute;
width: 240px;
background-color: gainsboro;
color: grey;
padding-top: 4px;
font-family: var(--code-font);
font-size: 0.8rem;
margin: 0;
padding: 0;
border-left: 4px solid #4b9edd;
}
pre .inline-completions ul.dropdown::before {
background-color: #4b9edd;
width: 2px;
position: absolute;
top: -1.2rem;
left: -3px;
content: " ";
}
pre .inline-completions ul.dropdown li {
overflow-x: hidden;
padding-left: 4px;
margin-bottom: 4px;
}
pre .inline-completions ul.dropdown li.deprecated {
text-decoration: line-through;
}
pre .inline-completions ul.dropdown li span.result-found {
color: #4b9edd;
}
pre .inline-completions ul.dropdown li span.result {
width: 100px;
color: black;
display: inline-block;
}
.dark-theme .markdown pre {
background-color: #d8d8d8;
border-color: #ddd;
filter: invert(98%) hue-rotate(180deg);
}
data-lsp {
/* Ensures there's no 1px jump when the hover happens */
border-bottom: 1px dotted transparent;
/* Fades in unobtrusively */
transition-timing-function: ease;
transition: border-color 0.3s;
}
/* Respect people's wishes to not have animations */
@media (prefers-reduced-motion: reduce) {
data-lsp {
transition: none;
}
}
/** Annotations support, providing a tool for meta commentary */
.tag-container {
position: relative;
}
.tag-container .twoslash-annotation {
position: absolute;
font-family:
"JetBrains Mono",
Menlo,
Monaco,
Consolas,
Courier New,
monospace;
right: -10px;
/** Default annotation text to 200px */
width: 200px;
color: #187abf;
background-color: #fcf3d9 bb;
}
.tag-container .twoslash-annotation p {
text-align: left;
font-size: 0.8rem;
line-height: 0.9rem;
}
.tag-container .twoslash-annotation svg {
float: left;
margin-left: -44px;
}
.tag-container .twoslash-annotation.left {
right: auto;
left: -200px;
}
.tag-container .twoslash-annotation.left svg {
float: right;
margin-right: -5px;
}
/** Support for showing console log/warn/errors inline */
pre .logger {
display: flex;
align-items: center;
color: black;
padding: 6px;
padding-left: 8px;
width: calc(100% - 19px);
white-space: pre-wrap;
}
pre .logger svg {
margin-right: 9px;
}
pre .logger.error-log {
background-color: #fee;
border-left: 2px solid #bf1818;
}
pre .logger.warn-log {
background-color: #ffe;
border-left: 2px solid #eae662;
}
pre .logger.log-log {
background-color: #e9e9e9;
border-left: 2px solid #ababab;
}
pre .logger.log-log svg {
margin-left: 6px;
margin-right: 9px;
}
body {
--shiki-color-text: #606060;
--shiki-color-background: transparent;
--shiki-token-constant: #00a5a5;
--shiki-token-string: #4e3a2c;
--shiki-token-comment: #aaa;
--shiki-token-keyword: #7b8bff;
--shiki-token-parameter: #ff9800;
--shiki-token-function: #445dd7;
--shiki-token-string-expression: #38a35f;
--shiki-token-punctuation: #969696;
--shiki-token-link: #1aa245;
}
.dark body {
--shiki-color-text: #d1d1d1;
--shiki-token-constant: #2dc9c9;
--shiki-token-string: #feb179;
--shiki-token-comment: #6b737c;
--shiki-token-keyword: #7b8bff;
--shiki-token-parameter: #ff9800;
--shiki-token-function: #9babff;
--shiki-token-string-expression: #42bb69;
--shiki-token-punctuation: #bbb;
--shiki-token-link: #ffab70;
@layer components {
@import "shiki.css";
}

View File

@@ -57,7 +57,8 @@ export default function RootLayout({
manrope.variable,
commitMono.variable,
inter.className,
"flex flex-col items-center bg-stone-50 text-stone-700 dark:text-stone-400 dark:bg-stone-950",
"flex flex-col items-center [&_*]:scroll-mt-[5rem]",
"bg-stone-50 text-stone-700 dark:text-stone-400 dark:bg-stone-950",
].join(" ")}
>
<SpeedInsights />
@@ -69,7 +70,7 @@ export default function RootLayout({
disableTransitionOnChange
>
<JazzNav />
<main className="flex min-h-screen flex-col container w-full">
<main className="flex flex-col container w-full">
{children}
</main>
<JazzFooter />

View File

@@ -0,0 +1,335 @@
pre.shiki {
overflow: hidden;
}
pre.shiki:hover .dim {
opacity: 1;
}
pre.shiki div.dim {
opacity: 0.5;
}
pre.shiki div.dim,
pre.shiki div.highlight {
margin: 0;
padding: 0;
}
pre.shiki div.highlight {
opacity: 1;
background-color: #f1f8ff;
}
pre.shiki div.line {
/* min-height: 1rem; */
counter-increment: lineNumber 1;
}
pre.shiki div.line::before {
content: counter(lineNumber);
display: inline-block;
vertical-align: middle;
width: 1.3rem;
padding-right: 0.3rem;
text-align: right;
transition: color 0.3s;
@apply text-stone-200/70 dark:text-stone-900 text-[0.65rem];
}
pre.shiki div.line:hover::before {
@apply text-stone-400 dark:text-stone-600;
}
/** Don't show the language identifiers */
pre.shiki .language-id {
display: none;
}
/** When you mouse over the pre, show the underlines */
pre.twoslash:hover data-lsp {
@apply border-dotted border-b border-stone-300 dark:border-stone-700;
}
/** The tooltip-like which provides the LSP response */
pre.twoslash data-lsp::before {
content: attr(lsp);
position: absolute;
transform: translate(0, 1.2rem);
max-width: 30rem;
@apply text-xs px-1.5 py-1 rounded border border-stone-200 dark:border-stone-900 shadow-lg overflow-hidden whitespace-pre-wrap text-stone-700 bg-stone-50 dark:text-stone-200 dark:bg-stone-950;
text-align: left;
z-index: 100;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
display: none;
}
pre.twoslash data-lsp:hover::before {
display: block;
opacity: 1;
pointer-events: visible;
width: auto;
height: auto;
}
.shiki-outer {
@apply shadow-sm rounded-xl;
}
.shiki-filename {
@apply px-3 py-2 bg-stone-100 text-stone-700 dark:bg-stone-900 dark:text-stone-300 rounded-t-xl text-xs;
}
pre .code-container {
@apply overflow-auto p-2 pl-0 bg-white dark:bg-stone-925 rounded-b-xl text-xs dark:border-stone-900;
}
/* The try button */
pre .code-container > a {
position: absolute;
right: 8px;
bottom: 8px;
border-radius: 4px;
border: 1px solid #719af4;
padding: 0 8px;
color: #719af4;
text-decoration: none;
opacity: 0;
transition-timing-function: ease;
transition: opacity 0.3s;
}
/* Respect no animations */
@media (prefers-reduced-motion: reduce) {
pre .code-container > a {
transition: none;
}
}
pre .code-container > a:hover {
color: white;
background-color: #719af4;
}
pre .code-container:hover a {
opacity: 1;
}
pre code {
white-space: pre;
}
pre code a {
text-decoration: none;
}
pre data-err {
/* Extracted from VS Code */
background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E")
repeat-x bottom left;
padding-bottom: 3px;
}
pre .query {
margin-bottom: 10px;
color: #137998;
display: inline-block;
}
/* In order to have the 'popped out' style design and to not break the layout
/* we need to place a fake and un-selectable copy of the error which _isn't_ broken out
/* behind the actual error message.
/* This sections keeps both of those two in in sync */
pre .error,
pre .error-behind {
margin-left: -14px;
margin-top: 8px;
margin-bottom: 4px;
padding: 6px;
padding-left: 14px;
width: calc(100% - 20px);
white-space: pre-wrap;
display: block;
}
pre .error {
position: absolute;
background-color: #fee;
border-left: 2px solid #bf1818;
/* Give the space to the error code */
display: flex;
align-items: center;
color: black;
}
pre .error .code {
display: none;
}
pre .error-behind {
user-select: none;
visibility: transparent;
color: #fee;
}
/* Queries */
pre .arrow {
/* Transparent background */
background-color: #eee;
position: relative;
top: -7px;
margin-left: 0.1rem;
/* Edges */
border-left: 1px solid #eee;
border-top: 1px solid #eee;
transform: translateY(25%) rotate(45deg);
/* Size */
height: 8px;
width: 8px;
}
pre .popover {
margin-bottom: 10px;
background-color: #eee;
display: inline-block;
padding: 0 0.5rem 0.3rem;
margin-top: 10px;
border-radius: 3px;
}
/* Completion */
pre .inline-completions ul.dropdown {
display: inline-block;
position: absolute;
width: 240px;
background-color: gainsboro;
color: grey;
padding-top: 4px;
font-family: var(--code-font);
font-size: 0.8rem;
margin: 0;
padding: 0;
border-left: 4px solid #4b9edd;
}
pre .inline-completions ul.dropdown::before {
background-color: #4b9edd;
width: 2px;
position: absolute;
top: -1.2rem;
left: -3px;
content: " ";
}
pre .inline-completions ul.dropdown li {
overflow-x: hidden;
padding-left: 4px;
margin-bottom: 4px;
}
pre .inline-completions ul.dropdown li.deprecated {
text-decoration: line-through;
}
pre .inline-completions ul.dropdown li span.result-found {
color: #4b9edd;
}
pre .inline-completions ul.dropdown li span.result {
width: 100px;
color: black;
display: inline-block;
}
.dark-theme .markdown pre {
background-color: #d8d8d8;
border-color: #ddd;
filter: invert(98%) hue-rotate(180deg);
}
data-lsp {
/* Ensures there's no 1px jump when the hover happens */
border-bottom: 1px dotted transparent;
/* Fades in unobtrusively */
transition-timing-function: ease;
transition: border-color 0.3s;
}
/* Respect people's wishes to not have animations */
@media (prefers-reduced-motion: reduce) {
data-lsp {
transition: none;
}
}
/** Annotations support, providing a tool for meta commentary */
.tag-container {
position: relative;
}
.tag-container .twoslash-annotation {
position: absolute;
font-family:
"JetBrains Mono",
Menlo,
Monaco,
Consolas,
Courier New,
monospace;
right: -10px;
/** Default annotation text to 200px */
width: 200px;
color: #187abf;
background-color: #fcf3d9 bb;
}
.tag-container .twoslash-annotation p {
text-align: left;
font-size: 0.8rem;
line-height: 0.9rem;
}
.tag-container .twoslash-annotation svg {
float: left;
margin-left: -44px;
}
.tag-container .twoslash-annotation.left {
right: auto;
left: -200px;
}
.tag-container .twoslash-annotation.left svg {
float: right;
margin-right: -5px;
}
/** Support for showing console log/warn/errors inline */
pre .logger {
display: flex;
align-items: center;
color: black;
padding: 6px;
padding-left: 8px;
width: calc(100% - 19px);
white-space: pre-wrap;
}
pre .logger svg {
margin-right: 9px;
}
pre .logger.error-log {
background-color: #fee;
border-left: 2px solid #bf1818;
}
pre .logger.warn-log {
background-color: #ffe;
border-left: 2px solid #eae662;
}
pre .logger.log-log {
background-color: #e9e9e9;
border-left: 2px solid #ababab;
}
pre .logger.log-log svg {
margin-left: 6px;
margin-right: 9px;
}
body {
--shiki-color-text: #606060;
--shiki-color-background: transparent;
--shiki-token-constant: #00a5a5;
--shiki-token-string: #4e3a2c;
--shiki-token-comment: #aaa;
--shiki-token-keyword: #7b8bff;
--shiki-token-parameter: #ff9800;
--shiki-token-function: #445dd7;
--shiki-token-string-expression: #38a35f;
--shiki-token-punctuation: #969696;
--shiki-token-link: #1aa245;
}
.dark body {
--shiki-color-text: #d1d1d1;
--shiki-token-constant: #2dc9c9;
--shiki-token-string: #feb179;
--shiki-token-comment: #6b737c;
--shiki-token-keyword: #7b8bff;
--shiki-token-parameter: #ff9800;
--shiki-token-function: #9babff;
--shiki-token-string-expression: #42bb69;
--shiki-token-punctuation: #bbb;
--shiki-token-link: #ffab70;
}

View File

@@ -22,12 +22,12 @@ export function CodeExampleTabs({
return (
<div
className={clsx(
"shadow-sm bg-white border rounded-sm overflow-hidden h-[40rem] max-h-[80vh] flex flex-col",
"dark:bg-stone-925 dark:border-stone-800",
"bg-white h-[40rem] max-h-[80vh] flex flex-col",
"dark:bg-stone-925",
className,
)}
>
<div className="flex border-b overflow-x-auto dark:border-stone-800 dark:bg-stone-900">
<div className="flex border-b overflow-x-auto overflow-y-hidden dark:border-stone-900 dark:bg-stone-900">
{tabs.map((tab, index) => (
<div key={index}>
<button

View File

@@ -1,6 +1,7 @@
"use client";
import { useLayoutEffect, useState, useRef, IframeHTMLAttributes } from "react";
import { CopyIcon } from "lucide-react";
export function ResponsiveIframe(
props: IframeHTMLAttributes<HTMLIFrameElement> & { localSrc: string },
@@ -52,31 +53,51 @@ export function ResponsiveIframe(
);
}, [props.src, props.localSrc]);
const copyUrl = () => {
if (url) {
navigator.clipboard.writeText(url);
}
};
return (
<div
className={
"w-full h-full flex flex-col items-stretch border dark:border-stone-800 " +
props.className
}
>
<div className="rounded-t-sm bg-white border-b dark:border-stone-800 dark:bg-stone-900 py-1.5 px-10 flex">
<>
<div className="bg-white flex gap-3 border-b dark:border-stone-900 text-xs dark:bg-stone-925">
<input
className="text-xs px-1 py-0.5 bg-white dark:bg-stone-900 outline outline-1 outline-stone-200 dark:outline-stone-800 w-full rounded text-center"
className="flex-1 font-mono bg-transparent overflow-hidden text-ellipsis py-2 px-3"
value={url?.replace("http://", "").replace("https://", "")}
onClick={(e) => e.currentTarget.select()}
onBlur={(e) => e.currentTarget.setSelectionRange(0, 0)}
readOnly
/>
{url?.includes("/#/chat/") && (
<button
type="button"
className="text-blue-600 flex items-center gap-1.5 py-2 px-3"
onClick={copyUrl}
>
<CopyIcon className="hidden sm:inline" size={12} />
<span>
Copy URL{" "}
<span className="hidden sm:inline">
to invite others
</span>
</span>
</button>
)}
</div>
<div className="flex-grow" ref={containerRef}>
<iframe
{...props}
src={src}
className="dark:bg-black"
{...dimensions}
allowFullScreen
/>
<div className="flex-1 bg-stone-100 flex items-stretch justify-center p-2 sm:p-6 dark:bg-stone-925">
<div className="border rounded-lg overflow-hidden shadow-2xl w-[20rem] min-h-[30rem] dark:border-stone-900">
<div className="h-full" ref={containerRef}>
<iframe
{...props}
src={src}
className="dark:bg-black w-full"
{...dimensions}
allowFullScreen
/>
</div>
</div>
</div>
</div>
</>
);
}

View File

@@ -1,86 +1,169 @@
import { requestProject } from "./requestProject";
import { PackageIcon } from "lucide-react";
import { ChevronRight, PackageIcon } from "lucide-react";
import { packages } from "@/lib/packages";
import Link from "next/link";
import { ReactNode } from "react";
import { clsx } from "clsx";
export function DocNav() {
export function DocNav({ className }: { className?: string }) {
const comingSoon = [
"Groups & Permissions",
"Auth, Accounts & Migrations",
"Edit Metadata & Time Travel",
"Backend Workers",
];
const covaluesItems = [
{
name: "Declaration",
href: "/docs#declaring-covalues",
},
{
name: "Reading",
href: "/docs#reading-covalues",
},
{
name: "Creation",
href: "/docs#creating-covalues",
},
{
name: "Editing & Subscription",
href: "/docs#editing-and-subscription",
},
{
name: "Persistence",
href: "/docs#persistence",
},
{
name: "Remote Sync",
href: "/docs#remote-sync",
},
{
name: "Simple Public Sharing",
href: "/docs#simple-public-sharing",
},
];
const refsItems = [
{
name: "Precise Loading Depths",
href: "/docs#loading-depth",
},
];
const groupsItems = [
{
name: "Groups/Accounts as Scopes",
href: "/docs#groups-accounts-as-scopes",
},
{
name: "Creating Invites",
href: "/docs#creating-invites",
},
{
name: "Consuming Invites",
href: "/docs#consuming-invites",
},
];
return (
<>
<p className="mt-0 font-medium">
<DocNavLink href="/docs">Guide</DocNavLink>
</p>
<ul>
<li>
<DocNavLink href="/docs#guide-setup">
Project Setup
</DocNavLink>
</li>
<li>
<DocNavLink href="/docs#intro-to-covalues">
Intro to CoValues
</DocNavLink>
<ul>
<li>
<DocNavLink href="/docs#declaring-covalues">
Declaration
</DocNavLink>
<div className={clsx(className, "text-sm space-y-5 pr-3")}>
<div>
<DocNavHeader href="/docs">Guide</DocNavHeader>
<ul>
<li>
<DocNavLink href="/docs#guide-setup">
Project Setup
</DocNavLink>
</li>
<li>
<DocNavLink href="/docs#intro-to-covalues">
Intro to CoValues
</DocNavLink>
<ul>
{covaluesItems.map((item) => (
<li key={item.name}>
<DocNavLink
className="pl-4"
href={item.href}
>
{item.name}
</DocNavLink>
</li>
))}
</ul>
</li>
<li>
<DocNavLink href="/docs#refs-and-on-demand-subscribe">
Refs & Auto-Subscribe
</DocNavLink>
<ul>
{refsItems.map((item) => (
<li key={item.name}>
<DocNavLink
className="pl-4"
href={item.href}
>
{item.name}
</DocNavLink>
</li>
))}
</ul>
</li>
<li>
<DocNavLink href="/docs#groups-and-permissions">
Groups & Permissions
</DocNavLink>
<ul>
{groupsItems.map((item) => (
<li key={item.name}>
<DocNavLink
className="pl-4"
href={item.href}
>
{item.name}
</DocNavLink>
</li>
))}
</ul>
</li>
</ul>
</div>
<div>
<DocNavHeader>Coming soon</DocNavHeader>
<ul>
{comingSoon.map((item) => (
<li className="py-1" key={item}>
{item}
</li>
<li>
<DocNavLink href="/docs#reading-covalues">
Reading
</DocNavLink>
))}
</ul>
</div>
<div>
<DocNavHeader>Resources</DocNavHeader>
<ul>
<li>
<DocNavLink href="/docs/resources/examples">
Example Apps
</DocNavLink>
</li>
</ul>
</div>
<div>
<DocNavHeader href="/docs/api-reference">
API Reference
</DocNavHeader>
<ul className="space-y-8">
{packages.map(({ name }) => (
<li key={name}>
<NavPackage package={name} />
</li>
<li>
<DocNavLink href="/docs#creating-covalues">
Creation
</DocNavLink>
</li>
<li>
<DocNavLink href="/docs#editing-and-subscription">
Editing & Subscription
</DocNavLink>
</li>
<li>
<DocNavLink href="/docs#persistence">
Persistence
</DocNavLink>
</li>
<li>
<DocNavLink href="/docs#remote-sync">
Remote Sync
</DocNavLink>
</li>
<li>
<DocNavLink href="/docs#simple-public-sharing">
Simple Public Sharing
</DocNavLink>
</li>
</ul>
</li>
<li>
<DocNavLink href="/docs#refs-and-on-demand-subscribe">
Refs & Auto-Subscribe
</DocNavLink>
</li>
</ul>
Coming soon:
<ul>
{comingSoon.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
{packages.map((packageName) => (
<NavPackage key={packageName} package={packageName} />
))}
</>
))}
</ul>
</div>
</div>
);
}
@@ -93,38 +176,38 @@ export async function NavPackage({
return (
<>
<Link
<DocNavLink
className="mb-1 flex gap-2 items-center"
href={`/docs/api-reference/${packageName}`}
className="text-sm mt-4 font-mono flex gap-1 items-center -mx-4 px-4 pt-4 border-t border-stone-200 dark:border-stone-900 "
>
{packageName}
<PackageIcon size={15} strokeWidth={1.5} />
</Link>
{packageName}
</DocNavLink>
{project.categories?.map((category) => {
return (
<details
key={category.title}
open={category.title !== "Other"}
className="[&:not([open])_summary]:after:content-['...']"
className="group ml-1.5 border-l dark:border-stone-900"
>
<summary className="block text-xs mt-2 mb-1 cursor-pointer">
<summary className="pl-[13px] py-1 cursor-pointer flex gap-2 items-center justify-between hover:text-stone-800 dark:hover:text-stone-200 [&::-webkit-details-marker]:hidden">
{category.title}
<ChevronRight className="w-4 h-4 text-stone-300 group-open:rotate-90 transition-transform dark:text-stone-800" />
</summary>
<div className="flex gap-1 flex-wrap text-balance">
<div className="pl-6">
{category.children.map(
(child, i, children) =>
(i == 0 ||
child.name !==
children[i - 1]!.name) && (
<>
<Link
key={child.id}
className="text-ellipsis overflow-hidden text-xs font-mono py-0.5 px-1.5 text-stone-800 dark:text-stone-200 bg-stone-200 dark:bg-stone-800 rounded opacity-70 hover:opacity-100"
href={`/docs/api-reference/${packageName}#${child.name}`}
>
{child.name}
</Link>
</>
<Link
key={child.id}
className="block py-0.5 text-ellipsis overflow-hidden font-mono hover:text-stone-800 dark:hover:text-stone-200"
href={`/docs/api-reference/${packageName}#${child.name}`}
>
{child.name}
</Link>
),
)}
</div>
@@ -138,20 +221,41 @@ export async function NavPackage({
export function DocNavLink({
href,
children,
className = "",
}: {
href: string;
children: ReactNode;
className?: string;
}) {
return (
<Link
href={href}
className={clsx(
className,
"py-1 hover:text-black dark:hover:text-stone-200 block hover:transition-colors",
)}
>
{children}
</Link>
);
}
function DocNavHeader({
href,
children,
}: {
href?: string;
children: ReactNode;
}) {
const className = "block font-medium text-stone-900 py-1 dark:text-white";
if (href) {
return (
<Link
href={href}
className="hover:text-black dark:hover:text-white py-1 hover:transition-colors"
>
<Link className={className} href={href}>
{children}
</Link>
);
}
return <span className="py-1">{children}</span>;
return <p className={className}>{children}</p>;
}

View File

@@ -1,4 +1,3 @@
import { readFile } from "fs/promises";
import { ProjectReflection, Deserializer, JSONOutput } from "typedoc";
import JazzToolsDocs from "../../typedoc/jazz-tools.json";

View File

@@ -1,7 +1,26 @@
export const packages = [
"jazz-tools",
"jazz-react",
"jazz-browser",
"jazz-browser-media-images",
"jazz-nodejs",
{
name: "jazz-tools",
description:
"The base implementation for Jazz. Provides a high-level API around the CoJSON protocol.",
},
{
name: "jazz-react",
description: "React bindings.",
},
{
name: "jazz-browser",
description:
"Browser bindings for writing Jazz apps using plain JavaScript, or to build your framework bindings for Jazz.",
},
{
name: "jazz-browser-media-images",
description:
"Support for creating ImageDefinition-compatible image sets from images provided as File or Blob objects.",
},
{
name: "jazz-nodejs",
description:
"Node.js bindings for writing server-side workers that interact with Jazz state.",
},
];

View File

@@ -45,7 +45,7 @@ function highlightPlugin() {
let lineNo = -1;
node.type = "html";
node.value = `<pre><code class="not-prose">${lines
node.value = `<pre><code class="not-prose py-2 flex flex-col leading-relaxed">${lines
.map((line) => {
const isSubduedLine = line.some((token) =>
token.content.includes("// old"),
@@ -57,11 +57,11 @@ function highlightPlugin() {
lineNo++;
}
return (
`<span class="line" style="${isBinnedLine ? "opacity: 0.3; text-decoration: line-through; user-select: none" : ""}"><div class="lineNo" style="${isSubduedLine ? "opacity: 0.3;" : ""}${isBinnedLine ? "color: red;" : ""}">${node.lang === "bash" ? ">" : isBinnedLine ? "✕" : (lineNo + 1)}</div>` +
`<span class="block px-3 ${isBinnedLine ? 'bg-red-100 dark:bg-red-800' : ''}" style="${isBinnedLine ? "user-select: none" : ""}">` +
line
.map(
(token) =>
`<span style="color: ${isBinnedLine ? "red" : token.color};${isSubduedLine ? "opacity: 0.3;" : ""}">${escape(token.content.replace("// old", "").replace("// *bin*", ""))}</span>`,
`<span style="color: ${token.color};${isSubduedLine ? "opacity: 0.4;" : ""}">${escape(token.content.replace("// old", "").replace("// *bin*", ""))}</span>`,
)
.join("") +
"</span>"

View File

@@ -8,6 +8,7 @@ const config: Config = {
"./codeSamples/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./next.config.mjs",
"./node_modules/gcmp-design-system/src/**/*.{js,ts,jsx,tsx,mdx}",
],
};

View File

@@ -1,30 +1,32 @@
{
"name": "cojson-storage-indexeddb",
"version": "0.8.0",
"main": "dist/index.js",
"type": "module",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:0.8.0",
"typescript": "^5.3.3"
},
"devDependencies": {
"@vitest/browser": "^0.34.1",
"fake-indexeddb": "^6.0.0",
"vitest": "1.5.3",
"webdriverio": "^8.15.0"
},
"scripts": {
"dev": "tsc --watch --sourceMap --outDir dist",
"lint": "eslint . --ext ts,tsx",
"format": "prettier --write './src/**/*.{ts,tsx}'",
"build": "npm run lint && rm -rf ./dist && tsc --sourceMap --outDir dist",
"prepublishOnly": "npm run build"
},
"lint-staged": {
"*.{ts,tsx}": "eslint --fix",
"*.{js,jsx,mdx,json}": "prettier --write"
},
"gitHead": "33c27053293b4801b968c61d5c4c989f93a67d13"
"name": "cojson-storage-indexeddb",
"version": "0.8.0",
"main": "dist/index.js",
"type": "module",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:0.8.0",
"typescript": "^5.3.3"
},
"devDependencies": {
"@vitest/browser": "^0.34.1",
"fake-indexeddb": "^6.0.0",
"vitest": "1.5.3",
"webdriverio": "^8.15.0"
},
"scripts": {
"dev": "tsc --watch --sourceMap --outDir dist",
"test": "vitest --run --root ../../ --project cojson-storage-indexeddb",
"test:watch": "vitest --watch --root ../../ --project cojson-storage-indexeddb",
"lint": "eslint . --ext ts,tsx",
"format": "prettier --write './src/**/*.{ts,tsx}'",
"build": "npm run lint && rm -rf ./dist && tsc --sourceMap --outDir dist",
"prepublishOnly": "npm run build"
},
"lint-staged": {
"*.{ts,tsx}": "eslint --fix",
"*.{js,jsx,mdx,json}": "prettier --write"
},
"gitHead": "33c27053293b4801b968c61d5c4c989f93a67d13"
}

View File

@@ -1,12 +1,8 @@
import 'fake-indexeddb/auto'; // Polyfill for IndexedDB
import "fake-indexeddb/auto"; // Polyfill for IndexedDB
import { expect, test } from "vitest";
import {
ControlledAgent,
LocalNode,
WasmCrypto,
} from "cojson";
import { IDBStorage } from "./index.js";
import { ControlledAgent, LocalNode, WasmCrypto } from "cojson";
import { IDBStorage } from "../index.js";
const Crypto = await WasmCrypto.create();

View File

@@ -1,26 +1,28 @@
{
"name": "cojson-transport-ws",
"type": "module",
"version": "0.8.0",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:0.8.0",
"typescript": "^5.3.3"
},
"scripts": {
"dev": "tsc --watch --sourceMap --outDir dist",
"lint": "eslint . --ext ts,tsx",
"format": "prettier --write './src/**/*.{ts,tsx}'",
"build": "npm run lint && rm -rf ./dist && tsc --sourceMap --outDir dist",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"@types/ws": "^8.5.5"
},
"lint-staged": {
"*.{ts,tsx}": "eslint --fix",
"*.{js,jsx,mdx,json}": "prettier --write"
}
"name": "cojson-transport-ws",
"type": "module",
"version": "0.8.0",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:0.8.0",
"typescript": "^5.3.3"
},
"scripts": {
"dev": "tsc --watch --sourceMap --outDir dist",
"test": "vitest --run --root ../../ --project cojson-transport-ws",
"test:watch": "vitest --watch --root ../../ --project cojson-transport-ws",
"lint": "eslint . --ext ts,tsx",
"format": "prettier --write './src/**/*.{ts,tsx}'",
"build": "npm run lint && rm -rf ./dist && tsc --sourceMap --outDir dist",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"@types/ws": "^8.5.5"
},
"lint-staged": {
"*.{ts,tsx}": "eslint --fix",
"*.{js,jsx,mdx,json}": "prettier --write"
}
}

View File

@@ -1,6 +1,10 @@
import { describe, test, expect, vi, Mocked } from "vitest";
import { BUFFER_LIMIT, BUFFER_LIMIT_POLLING_INTERVAL, createWebSocketPeer } from "./index.js";
import { AnyWebSocket, PingMsg } from "./types.js";
import {
BUFFER_LIMIT,
BUFFER_LIMIT_POLLING_INTERVAL,
createWebSocketPeer,
} from "../index.js";
import { AnyWebSocket, PingMsg } from "../types.js";
import { SyncMessage } from "cojson";
import { Channel } from "cojson/src/streamUtils";
@@ -47,8 +51,14 @@ describe("createWebSocketPeer", () => {
test("should handle ping messages", async () => {
const { listeners } = setup();
const pingMessage: PingMsg = { type: "ping", time: Date.now(), dc: "test-dc" };
const messageEvent = new MessageEvent("message", { data: JSON.stringify(pingMessage) });
const pingMessage: PingMsg = {
type: "ping",
time: Date.now(),
dc: "test-dc",
};
const messageEvent = new MessageEvent("message", {
data: JSON.stringify(pingMessage),
});
const messageHandler = listeners.get("message");
@@ -67,7 +77,9 @@ describe("createWebSocketPeer", () => {
const { listeners, peer } = setup();
const incoming = peer.incoming as Channel<SyncMessage | "Disconnected" | "PingTimeout">;
const incoming = peer.incoming as Channel<
SyncMessage | "Disconnected" | "PingTimeout"
>;
const pushSpy = vi.spyOn(incoming, "push");
const closeHandler = listeners.get("close");
@@ -81,7 +93,9 @@ describe("createWebSocketPeer", () => {
vi.useFakeTimers();
const { listeners, peer } = setup();
const incoming = peer.incoming as Channel<SyncMessage | "Disconnected" | "PingTimeout">;
const incoming = peer.incoming as Channel<
SyncMessage | "Disconnected" | "PingTimeout"
>;
const pushSpy = vi.spyOn(incoming, "push");
const messageHandler = listeners.get("message");
@@ -98,17 +112,22 @@ describe("createWebSocketPeer", () => {
test("should send outgoing messages", async () => {
const { peer, mockWebSocket } = setup();
const testMessage: SyncMessage = { action: "known", id: "co_ztest", header: false, sessions: {} };
const testMessage: SyncMessage = {
action: "known",
id: "co_ztest",
header: false,
sessions: {},
};
const promise = peer.outgoing.push(testMessage);
await new Promise<void>(queueMicrotask);
expect(mockWebSocket.send).toHaveBeenCalledWith(JSON.stringify(testMessage));
expect(mockWebSocket.send).toHaveBeenCalledWith(
JSON.stringify(testMessage),
);
await expect(promise).resolves.toBeUndefined();
});
test("should stop sending messages when the websocket is closed", async () => {
const { peer, mockWebSocket } = setup();
@@ -116,16 +135,32 @@ describe("createWebSocketPeer", () => {
mockWebSocket.readyState = 0;
});
const message1: SyncMessage = { action: "known", id: "co_ztest", header: false, sessions: {} };
const message2: SyncMessage = { action: "content", id: "co_zlow", new: {}, priority: 1 };
const message1: SyncMessage = {
action: "known",
id: "co_ztest",
header: false,
sessions: {},
};
const message2: SyncMessage = {
action: "content",
id: "co_zlow",
new: {},
priority: 1,
};
void peer.outgoing.push(message1);
void peer.outgoing.push(message2);
await new Promise<void>(queueMicrotask);
expect(mockWebSocket.send).toHaveBeenNthCalledWith(1, JSON.stringify(message1));
expect(mockWebSocket.send).not.toHaveBeenNthCalledWith(2, JSON.stringify(message2));
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
1,
JSON.stringify(message1),
);
expect(mockWebSocket.send).not.toHaveBeenNthCalledWith(
2,
JSON.stringify(message2),
);
});
test("should wait for the buffer to be under BUFFER_LIMIT before sending more messages", async () => {
@@ -136,22 +171,41 @@ describe("createWebSocketPeer", () => {
mockWebSocket.bufferedAmount = BUFFER_LIMIT + 1;
});
const message1: SyncMessage = { action: "known", id: "co_ztest", header: false, sessions: {} };
const message2: SyncMessage = { action: "content", id: "co_zlow", new: {}, priority: 1 };
const message1: SyncMessage = {
action: "known",
id: "co_ztest",
header: false,
sessions: {},
};
const message2: SyncMessage = {
action: "content",
id: "co_zlow",
new: {},
priority: 1,
};
void peer.outgoing.push(message1);
void peer.outgoing.push(message2);
await new Promise<void>(queueMicrotask);
expect(mockWebSocket.send).toHaveBeenNthCalledWith(1, JSON.stringify(message1));
expect(mockWebSocket.send).not.toHaveBeenNthCalledWith(2, JSON.stringify(message2));
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
1,
JSON.stringify(message1),
);
expect(mockWebSocket.send).not.toHaveBeenNthCalledWith(
2,
JSON.stringify(message2),
);
mockWebSocket.bufferedAmount = 0;
await vi.advanceTimersByTimeAsync(BUFFER_LIMIT_POLLING_INTERVAL + 1);
expect(mockWebSocket.send).toHaveBeenNthCalledWith(2, JSON.stringify(message2));
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
2,
JSON.stringify(message2),
);
vi.useRealTimers();
});
@@ -163,4 +217,4 @@ describe("createWebSocketPeer", () => {
expect(mockWebSocket.close).toHaveBeenCalled();
});
});
});

View File

@@ -29,8 +29,8 @@
},
"scripts": {
"dev": "tsc --watch --sourceMap --outDir dist",
"test": "vitest --run",
"test-watch": "vitest",
"test": "vitest --run --root ../../ --project cojson",
"test:watch": "vitest --watch --root ../../ --project cojson",
"lint": "eslint . --ext ts,tsx",
"format": "prettier --write './src/**/*.{ts,tsx}'",
"build": "npm run lint && rm -rf ./dist && tsc --sourceMap --outDir dist",

View File

@@ -1,5 +1,13 @@
# jazz-browser-media-images
## 0.8.2
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
- jazz-browser@0.8.2
## 0.8.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser-auth-clerk",
"version": "0.8.1",
"version": "0.8.2",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
@@ -11,8 +11,8 @@
},
"dependencies": {
"cojson": "workspace:0.8.0",
"jazz-browser": "workspace:0.8.1",
"jazz-tools": "workspace:0.8.1"
"jazz-browser": "workspace:0.8.2",
"jazz-tools": "workspace:0.8.2"
},
"scripts": {
"lint": "eslint . --ext ts,tsx",

View File

@@ -1,5 +1,13 @@
# jazz-browser-media-images
## 0.8.2
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
- jazz-browser@0.8.2
## 0.8.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser-media-images",
"version": "0.8.1",
"version": "0.8.2",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
@@ -8,8 +8,8 @@
"dependencies": {
"@types/image-blob-reduce": "^4.1.1",
"image-blob-reduce": "^4.1.0",
"jazz-browser": "workspace:0.8.1",
"jazz-tools": "workspace:0.8.1",
"jazz-browser": "workspace:0.8.2",
"jazz-tools": "workspace:0.8.2",
"pica": "^9.0.1",
"typescript": "^5.3.3"
},

View File

@@ -1,5 +1,12 @@
# jazz-browser
## 0.8.2
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
## 0.8.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser",
"version": "0.8.1",
"version": "0.8.2",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
@@ -10,7 +10,7 @@
"cojson": "workspace:0.8.0",
"cojson-storage-indexeddb": "workspace:0.8.0",
"cojson-transport-ws": "workspace:0.8.0",
"jazz-tools": "workspace:0.8.1",
"jazz-tools": "workspace:0.8.2",
"typescript": "^5.3.3"
},
"scripts": {

View File

@@ -1,5 +1,12 @@
# jazz-autosub
## 0.8.2
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
## 0.8.1
### Patch Changes

View File

@@ -5,11 +5,11 @@
"types": "src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.8.1",
"version": "0.8.2",
"dependencies": {
"cojson": "workspace:0.8.0",
"cojson-transport-ws": "workspace:0.8.0",
"jazz-tools": "workspace:0.8.1",
"jazz-tools": "workspace:0.8.2",
"ws": "^8.14.2"
},
"devDependencies": {

View File

@@ -1,5 +1,14 @@
# jazz-browser-media-images
## 0.8.2
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
- jazz-browser-auth-clerk@0.8.2
- jazz-react@0.8.2
## 0.8.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-auth-clerk",
"version": "0.8.1",
"version": "0.8.2",
"type": "module",
"main": "dist/index.js",
"types": "src/index.tsx",
@@ -11,9 +11,9 @@
},
"dependencies": {
"cojson": "workspace:0.8.0",
"jazz-browser-auth-clerk": "workspace:0.8.1",
"jazz-react": "workspace:0.8.1",
"jazz-tools": "workspace:0.8.1"
"jazz-browser-auth-clerk": "workspace:0.8.2",
"jazz-react": "workspace:0.8.2",
"jazz-tools": "workspace:0.8.2"
},
"peerDependencies": {
"react": "^18.2.0"

View File

@@ -1,5 +1,13 @@
# jazz-react
## 0.8.2
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
- jazz-browser@0.8.2
## 0.8.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react",
"version": "0.8.1",
"version": "0.8.2",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
@@ -8,8 +8,8 @@
"dependencies": {
"@scure/bip39": "^1.3.0",
"cojson": "workspace:0.8.0",
"jazz-browser": "workspace:0.8.1",
"jazz-tools": "workspace:0.8.1",
"jazz-browser": "workspace:0.8.2",
"jazz-tools": "workspace:0.8.2",
"typescript": "^5.3.3"
},
"devDependencies": {

View File

@@ -1,5 +1,12 @@
# jazz-run
## 0.8.2
### Patch Changes
- Updated dependencies [a075f90]
- jazz-tools@0.8.2
## 0.8.1
### Patch Changes

View File

@@ -3,7 +3,7 @@
"bin": "./dist/index.js",
"type": "module",
"license": "MIT",
"version": "0.8.1",
"version": "0.8.2",
"scripts": {
"lint": "eslint . --ext ts,tsx",
"format": "prettier --write './src/**/*.{ts,tsx}'",
@@ -21,7 +21,7 @@
"cojson-storage-sqlite": "workspace:0.8.0",
"cojson-transport-ws": "workspace:0.8.0",
"effect": "^3.6.5",
"jazz-tools": "workspace:0.8.1",
"jazz-tools": "workspace:0.8.2",
"ws": "^8.14.2"
},
"devDependencies": {

View File

@@ -1,5 +1,11 @@
# jazz-tools
## 0.8.2
### Patch Changes
- a075f90: Fixed cursor reset when interacting with text inputs
## 0.8.1
### Patch Changes

View File

@@ -5,14 +5,14 @@
"types": "./src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.8.1",
"version": "0.8.2",
"dependencies": {
"cojson": "workspace:0.8.0",
"fast-check": "^3.17.2"
},
"scripts": {
"test": "vitest --run",
"test-watch": "vitest",
"test": "vitest --run --root ../../ --project jazz-tools",
"test:watch": "vitest --watch --root ../../ --project jazz-tools",
"lint": "eslint . --ext ts,tsx",
"format": "prettier --write './src/**/*.{ts,tsx}'",
"build": "npm run lint && rm -rf ./dist && tsc --sourceMap --outDir dist",

View File

@@ -62,13 +62,7 @@ export class SubscriptionScope<Root extends CoValue> {
}
scheduleUpdate() {
if (!this.scheduledUpdate) {
this.scheduledUpdate = true;
queueMicrotask(() => {
this.scheduledUpdate = false;
this.onUpdate(this.rootEntry.value);
});
}
this.onUpdate(this.rootEntry.value);
}
onRefAccessedOrSet(

60
pnpm-lock.yaml generated
View File

@@ -79,6 +79,61 @@ importers:
specifier: ^5.0.10
version: 5.0.10(@types/node@22.5.1)
e2e/CoValues:
dependencies:
cojson:
specifier: workspace:*
version: link:../../packages/cojson
hash-slash:
specifier: workspace:*
version: link:../../packages/hash-slash
is-ci:
specifier: ^3.0.1
version: 3.0.1
jazz-react:
specifier: workspace:*
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:*
version: link:../../packages/jazz-tools
react:
specifier: ^18.2.0
version: 18.2.0
react-dom:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
react-router:
specifier: ^6.16.0
version: 6.21.0(react@18.2.0)
react-router-dom:
specifier: ^6.16.0
version: 6.21.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
devDependencies:
'@playwright/test':
specifier: ^1.46.1
version: 1.46.1
'@types/node':
specifier: ^22.5.1
version: 22.5.1
'@types/react':
specifier: ^18.2.19
version: 18.2.45
'@types/react-dom':
specifier: ^18.2.7
version: 18.2.18
'@vitejs/plugin-react-swc':
specifier: ^3.3.2
version: 3.5.0(vite@5.0.10(@types/node@22.5.1))
jstat:
specifier: ^1.9.6
version: 1.9.6
typescript:
specifier: ^5.3.3
version: 5.3.3
vite:
specifier: ^5.0.10
version: 5.0.10(@types/node@22.5.1)
examples/chat:
dependencies:
'@radix-ui/react-checkbox':
@@ -3700,6 +3755,9 @@ packages:
jsonfile@4.0.0:
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
jstat@1.9.6:
resolution: {integrity: sha512-rPBkJbK2TnA8pzs93QcDDPlKcrtZWuuCo2dVR0TFLOJSxhqfWOVCSp8aV3/oSbn+4uY4yw1URtLpHQedtmXfug==}
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -8175,6 +8233,8 @@ snapshots:
optionalDependencies:
graceful-fs: 4.2.11
jstat@1.9.6: {}
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1