Compare commits
2 Commits
cojson@0.8
...
background
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff44f51a14 | ||
|
|
a9d2c82557 |
51
.github/workflows/jazz-run.yml
vendored
@@ -1,51 +0,0 @@
|
||||
name: Jazz Run Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build jazz-run
|
||||
run: pnpm exec turbo build && chmod +x dist/index.js;
|
||||
working-directory: ./packages/jazz-run
|
||||
|
||||
- name: Run create account
|
||||
run: ./dist/index.js account create --name "Jazz Run CI test"
|
||||
working-directory: ./packages/jazz-run
|
||||
|
||||
51
.github/workflows/release.yml
vendored
@@ -1,51 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Create Release Pull Request or Publish to npm
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
version: pnpm changeset-version
|
||||
publish: pnpm release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
6
.github/workflows/unit-test.yml
vendored
@@ -13,13 +13,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official email address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to [the community leaders responsible for enforcement](mailto:hello@gcmp.io).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
@@ -1,73 +0,0 @@
|
||||
# Contribution Guide
|
||||
|
||||
Thank you for considering contributing to Jazz! Jazz is an open-source framework for building local-first apps. We value your time and effort and are excited to collaborate with you. This guide will help you get started with contributing.
|
||||
|
||||
## How to Contribute
|
||||
|
||||
### 1. Reporting Bugs
|
||||
|
||||
If you find a bug, please [open an issue with as much detail as possible](https://github.com/gardencmp/jazz/issues). Include:
|
||||
|
||||
- A clear and descriptive title.
|
||||
- Steps to reproduce the issue.
|
||||
- What you expected to happen.
|
||||
- What actually happened.
|
||||
|
||||
### 2. Suggesting Enhancements
|
||||
|
||||
We welcome all ideas! If you have suggestions, feel free to open an issue marked with the "enhancement" label. Please provide context on why the enhancement would be beneficial and how it could be implemented.
|
||||
|
||||
### 3. Pull Requests
|
||||
|
||||
1. **Fork the repository** and create your feature branch (see [GitHub's guide on forking a repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo) if you're unfamiliar with the process):
|
||||
|
||||
2. **Make your changes**, ensuring that you follow our coding standards (`pnpm format` (prettier) and `pnpm lint` (eslint) will automatically let you know there are issues).
|
||||
|
||||
3. **Commit your changes** with a descriptive commit message.
|
||||
|
||||
4. **Push to your fork** and submit a pull request.
|
||||
|
||||
5. **Describe your pull request**, explaining the problem it solves or the enhancement it adds.
|
||||
|
||||
### 4. Code Style Guidelines
|
||||
|
||||
- We use [Prettier](https://prettier.io/) for formatting. Please ensure your code is formatted before submitting.
|
||||
- Write descriptive comments where necessary.
|
||||
|
||||
### 5. Local Setup
|
||||
|
||||
1. **Clone the repository**:
|
||||
```bash
|
||||
git clone https://github.com/gardencmp/jazz.git
|
||||
```
|
||||
|
||||
2. **Install dependencies**:
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. **Run tests** to verify everything is working:
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
### 6. Testing
|
||||
|
||||
Please write tests for any new features or bug fixes. We use Vitest for unit tests. Make sure all tests pass before submitting a pull request.
|
||||
|
||||
### 7. Communication
|
||||
|
||||
- If you're unsure about anything, feel free to ask questions by opening a discussion, reaching out via issues, or on our [Discord](https://discord.gg/utDMjHYg42).
|
||||
- Be respectful and constructive, this is a welcoming community for everyone.
|
||||
- Please be mindful of GitHub’s [Community Guidelines](https://docs.github.com/en/site-policy/github-terms/github-community-guidelines), which include being kind, avoiding disruptive behavior, and respecting others.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Please read and adhere to our [Code of Conduct](./CODE_OF_CONDUCT.md) to ensure a positive experience for all contributors.
|
||||
|
||||
---
|
||||
|
||||
Thank you again for your interest in contributing to Jazz. Your help makes this project better for everyone!
|
||||
|
||||
If you have any questions, don't hesitate to reach out. Let's make something great together!
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
# @jazz-e2e/binarycostream
|
||||
|
||||
## 0.0.89
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1ed4ab5]
|
||||
- cojson@0.8.11
|
||||
- jazz-react@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
|
||||
## 0.0.88
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@jazz-e2e/binarycostream",
|
||||
"private": true,
|
||||
"version": "0.0.89",
|
||||
"version": "0.0.88",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -14,11 +14,11 @@
|
||||
"*.{js,jsx,mdx,json}": "prettier --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"cojson": "workspace:0.8.11",
|
||||
"cojson": "workspace:0.8.5",
|
||||
"hash-slash": "workspace:0.2.1",
|
||||
"is-ci": "^3.0.1",
|
||||
"jazz-react": "workspace:0.8.11",
|
||||
"jazz-tools": "workspace:0.8.11",
|
||||
"jazz-react": "workspace:0.8.7",
|
||||
"jazz-tools": "workspace:0.8.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
# @jazz-e2e/covalues
|
||||
|
||||
## 0.0.88
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1ed4ab5]
|
||||
- cojson@0.8.11
|
||||
- jazz-react@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
|
||||
## 0.0.87
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@jazz-e2e/covalues",
|
||||
"private": true,
|
||||
"version": "0.0.88",
|
||||
"version": "0.0.87",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -27,11 +27,10 @@ export default defineConfig({
|
||||
/* 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: "http://localhost:5173/",
|
||||
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",
|
||||
permissions: ["clipboard-read", "clipboard-write"],
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
@@ -43,11 +42,8 @@ export default defineConfig({
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: [
|
||||
{
|
||||
command: "pnpm preview --port 5173",
|
||||
url: "http://localhost:5173/",
|
||||
reuseExistingServer: !isCI,
|
||||
},
|
||||
],
|
||||
webServer: isCI ? {
|
||||
command: "pnpm preview",
|
||||
url: "http://localhost:4173/",
|
||||
} : undefined,
|
||||
});
|
||||
|
||||
@@ -2,20 +2,15 @@ import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { AuthAndJazz } from "./jazz";
|
||||
import { TestInput } from "./pages/TestInput";
|
||||
import { ResumeSyncState } from "./pages/ResumeSyncState";
|
||||
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
||||
import { RouterProvider, createHashRouter } from "react-router-dom";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
const router = createHashRouter([
|
||||
{
|
||||
path: "/test-input",
|
||||
path: "/",
|
||||
element: <TestInput />,
|
||||
},
|
||||
{
|
||||
path: "/resume-sync",
|
||||
element: <ResumeSyncState />,
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
path: "/test-input",
|
||||
element: <TestInput />,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1,43 +1,25 @@
|
||||
import { createJazzReactApp, useDemoAuth } from "jazz-react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { createJazzReactApp } from "jazz-react";
|
||||
import { ephemeralCredentialsAuth } from "jazz-tools";
|
||||
import { useState } from "react";
|
||||
|
||||
const key = `test-comap@jazz.tools`;
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
const peer =
|
||||
(url.searchParams.get("peer") as `ws://${string}`) ??
|
||||
`wss://cloud.jazz.tools/`;
|
||||
const localSync = new URLSearchParams(location.search).has("localSync");
|
||||
|
||||
const Jazz = createJazzReactApp();
|
||||
|
||||
export const { useAccount, useCoState } = Jazz;
|
||||
|
||||
function getUserInfo() {
|
||||
return url.searchParams.get("userName") ?? "Mister X";
|
||||
}
|
||||
|
||||
export function AuthAndJazz({ children }: { children: React.ReactNode }) {
|
||||
const [auth, state] = useDemoAuth();
|
||||
const [ephemeralAuth] = useState(ephemeralCredentialsAuth())
|
||||
|
||||
const signedUp = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (state.state === "ready" && !signedUp.current) {
|
||||
const userName = getUserInfo();
|
||||
|
||||
if (state.existingUsers.includes(userName)) {
|
||||
state.logInAs(userName);
|
||||
} else {
|
||||
state.signUp(userName);
|
||||
}
|
||||
|
||||
signedUp.current = true;
|
||||
}
|
||||
}, [state.state]);
|
||||
|
||||
return (
|
||||
<Jazz.Provider auth={auth} peer={`${peer}?key=${key}`}>
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
);
|
||||
return (
|
||||
<Jazz.Provider auth={ephemeralAuth} peer={
|
||||
localSync
|
||||
? `ws://localhost:4200?key=${key}`
|
||||
: `wss://cloud.jazz.tools/?key=${key}`
|
||||
}>
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import { co, CoMap, Group, ID } from "jazz-tools";
|
||||
import { useAccount, useCoState } from "../jazz";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export class ResumeSyncCoMap extends CoMap {
|
||||
value = co.string;
|
||||
}
|
||||
|
||||
function getIdParam() {
|
||||
const url = new URL(window.location.href);
|
||||
return (url.searchParams.get("id") as ID<ResumeSyncCoMap>) ?? undefined;
|
||||
}
|
||||
|
||||
export function ResumeSyncState() {
|
||||
const [id, setId] = useState(getIdParam);
|
||||
const coMap = useCoState(ResumeSyncCoMap, id);
|
||||
const { me } = useAccount();
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("id", id);
|
||||
history.pushState({}, "", url.toString());
|
||||
}
|
||||
}, [id])
|
||||
|
||||
useEffect(() => {
|
||||
if (!me || id) return;
|
||||
|
||||
const group = Group.create({ owner: me });
|
||||
|
||||
group.addMember("everyone", "writer");
|
||||
|
||||
setId(ResumeSyncCoMap.create({ value: "" }, { owner: group }).id);
|
||||
}, [me]);
|
||||
|
||||
if (!coMap) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Resume Sync State</h1>
|
||||
<p data-testid="id">{coMap.id}</p>
|
||||
<label htmlFor="value">Value</label>
|
||||
<input
|
||||
id="value"
|
||||
value={coMap.value ?? ""}
|
||||
onChange={(e) => {
|
||||
coMap.value = e.target.value;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { setTimeout } from "node:timers/promises";
|
||||
|
||||
test.describe("ResumeSyncState", () => {
|
||||
test.skip("should resume the sync even after a page reload", async ({ page, browser }) => {
|
||||
const context = page.context();
|
||||
|
||||
await page.goto("/resume-sync?userName=SuperMario");
|
||||
|
||||
const id = await page.getByTestId("id").textContent();
|
||||
|
||||
// Sync an initial value
|
||||
await page.getByRole("textbox", { name: "Value" }).fill("Let's go!");
|
||||
await setTimeout(1000);
|
||||
|
||||
await context.setOffline(true);
|
||||
|
||||
// Change the value while offline
|
||||
await page.getByRole("textbox", { name: "Value" }).fill("Mammamia!");
|
||||
|
||||
// Navigate away from the page
|
||||
await page.goto(`about:blank`);
|
||||
|
||||
await setTimeout(1000);
|
||||
await context.setOffline(false);
|
||||
|
||||
// Reload the page but without loading the coValue
|
||||
// await page.goto(`/resume-sync?userName=SuperMario`);
|
||||
await page.goto(`/resume-sync?userName=SuperMario`);
|
||||
|
||||
await setTimeout(1000);
|
||||
|
||||
await expect(page.getByTestId("id")).toBeInViewport();
|
||||
|
||||
// Create a new incognito instance and try to load the coValue
|
||||
const newUserPage = await (await browser.newContext()).newPage();
|
||||
await newUserPage.goto(`/resume-sync?userName=Luigi&id=${id}`);
|
||||
|
||||
await expect(newUserPage.getByTestId("id")).toBeInViewport();
|
||||
|
||||
// The initial user should have synced the value even if the coValue was not loaded
|
||||
// when the user is back online
|
||||
await expect(newUserPage.getByRole("textbox", { name: "Value" })).toHaveValue("Mammamia!", {
|
||||
timeout: 20_000
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,5 @@
|
||||
# jazz-example-book-shelf
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
- jazz-browser-media-images@0.8.11
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-example-book-shelf",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -11,9 +11,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-browser-media-images": "workspace:0.8.11",
|
||||
"jazz-react": "workspace:0.8.11",
|
||||
"jazz-tools": "workspace:0.8.11",
|
||||
"jazz-browser-media-images": "workspace:0.8.7",
|
||||
"jazz-react": "workspace:0.8.7",
|
||||
"jazz-tools": "workspace:0.8.5",
|
||||
"next": "14.2.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.88
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1ed4ab5]
|
||||
- cojson@0.8.11
|
||||
- jazz-react@0.8.11
|
||||
- jazz-react-auth-clerk@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
|
||||
## 0.0.87
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-chat-clerk",
|
||||
"private": true,
|
||||
"version": "0.0.88",
|
||||
"version": "0.0.87",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -21,11 +21,11 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cojson": "workspace:0.8.11",
|
||||
"cojson": "workspace:0.8.5",
|
||||
"hash-slash": "workspace:0.2.1",
|
||||
"jazz-react": "workspace:0.8.11",
|
||||
"jazz-react-auth-clerk": "workspace:0.8.11",
|
||||
"jazz-tools": "workspace:0.8.11",
|
||||
"jazz-react": "workspace:0.8.7",
|
||||
"jazz-react-auth-clerk": "workspace:0.8.7",
|
||||
"jazz-tools": "workspace:0.8.5",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
# chat-rn-clerk
|
||||
|
||||
## 1.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.11
|
||||
- jazz-react-native@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
- jazz-react-native-media-images@0.8.7
|
||||
|
||||
## 1.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "chat-rn-clerk",
|
||||
"main": "index.js",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.3",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
# chat-rn
|
||||
|
||||
## 1.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
|
||||
## 1.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.5",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1ed4ab5]
|
||||
- cojson@0.8.11
|
||||
- jazz-react@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
|
||||
## 0.0.89
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-chat",
|
||||
"private": true,
|
||||
"version": "0.0.90",
|
||||
"version": "0.0.89",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -22,10 +22,10 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cojson": "workspace:0.8.11",
|
||||
"cojson": "workspace:0.8.5",
|
||||
"hash-slash": "workspace:0.2.1",
|
||||
"jazz-react": "workspace:0.8.11",
|
||||
"jazz-tools": "workspace:0.8.11",
|
||||
"jazz-react": "workspace:0.8.7",
|
||||
"jazz-tools": "workspace:0.8.5",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
# jazz-example-inspector
|
||||
|
||||
## 0.0.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1ed4ab5]
|
||||
- cojson@0.8.11
|
||||
- cojson-transport-ws@0.8.11
|
||||
|
||||
## 0.0.65
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-inspector",
|
||||
"private": true,
|
||||
"version": "0.0.66",
|
||||
"version": "0.0.65",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -15,8 +15,8 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cojson": "workspace:0.8.11",
|
||||
"cojson-transport-ws": "workspace:0.8.11",
|
||||
"cojson": "workspace:0.8.5",
|
||||
"cojson-transport-ws": "workspace:0.8.7",
|
||||
"hash-slash": "workspace:0.2.1",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
# jazz-example-musicplayer
|
||||
|
||||
## 0.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
|
||||
## 0.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-music-player",
|
||||
"private": true,
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.9",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -22,8 +22,8 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-react": "workspace:0.8.11",
|
||||
"jazz-tools": "workspace:0.8.11",
|
||||
"jazz-react": "workspace:0.8.7",
|
||||
"jazz-tools": "workspace:0.8.5",
|
||||
"lucide-react": "^0.274.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
# jazz-password-manager
|
||||
|
||||
## 0.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
|
||||
## 0.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-password-manager",
|
||||
"private": true,
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.8",
|
||||
"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.11",
|
||||
"jazz-tools": "workspace:0.8.11",
|
||||
"jazz-react": "workspace:0.8.7",
|
||||
"jazz-tools": "workspace:0.8.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.41.5",
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
# jazz-example-pets
|
||||
|
||||
## 0.0.107
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
- jazz-browser-media-images@0.8.11
|
||||
|
||||
## 0.0.106
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-pets",
|
||||
"private": true,
|
||||
"version": "0.0.107",
|
||||
"version": "0.0.106",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -23,9 +23,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.11",
|
||||
"jazz-react": "workspace:0.8.11",
|
||||
"jazz-tools": "workspace:0.8.11",
|
||||
"jazz-browser-media-images": "workspace:0.8.7",
|
||||
"jazz-react": "workspace:0.8.7",
|
||||
"jazz-tools": "workspace:0.8.5",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
@@ -50,7 +50,7 @@
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"is-ci": "^3.0.1",
|
||||
"jazz-run": "workspace:0.8.11",
|
||||
"jazz-run": "workspace:0.8.7",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "^5.3.3",
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
# jazz-example-todo
|
||||
|
||||
## 0.0.106
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
|
||||
## 0.0.105
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-todo",
|
||||
"private": true,
|
||||
"version": "0.0.106",
|
||||
"version": "0.0.105",
|
||||
"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.11",
|
||||
"jazz-tools": "workspace:0.8.11",
|
||||
"jazz-react": "workspace:0.8.7",
|
||||
"jazz-tools": "workspace:0.8.5",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.436.0",
|
||||
"next": "14.2.7",
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
|
||||
@@ -28,7 +28,7 @@ export function Button(props: ButtonProps) {
|
||||
primary:
|
||||
"bg-blue border-blue text-white font-medium bg-blue hover:bg-blue-800 hover:border-blue-800",
|
||||
secondary:
|
||||
"text-stone-900 border font-medium hover:border-stone-300 hover:dark:border-stone-700 dark:text-white",
|
||||
"text-stone-900 border border-stone-200 dark:border-stone-800 font-medium hover:border-stone-300 hover:dark:border-stone-700 dark:text-white",
|
||||
};
|
||||
|
||||
const classNames = clsx(
|
||||
|
||||
@@ -6,10 +6,12 @@ export function CodeRef({ children }: { children: React.ReactNode }) {
|
||||
className={clsx(
|
||||
"font-mono",
|
||||
"text-[0.9em]",
|
||||
"px-2 py-1",
|
||||
"rounded",
|
||||
"text-stone-900 dark:text-stone-200",
|
||||
"px-1 py-0.5",
|
||||
"rounded-sm",
|
||||
"border",
|
||||
"text-stone-800 dark:text-stone-200",
|
||||
"bg-stone-100 dark:bg-stone-800",
|
||||
"border-stone-200 dark:border-stone-900",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -6,7 +6,7 @@ export function GridCard(props: { children: ReactNode; className?: string }) {
|
||||
<div
|
||||
className={clsx(
|
||||
"col-span-2 p-4 [&>h4]:mt-0 [&>h3]:mt-0 [&>:last-child]:mb-0",
|
||||
"border rounded-xl shadow-sm",
|
||||
"border border-stone-200 dark:border-stone-900 rounded-xl shadow-sm",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -30,7 +30,7 @@ export function H2({ children, className }: HeadingProps) {
|
||||
className,
|
||||
"font-display",
|
||||
"text-stone-950 dark:text-white",
|
||||
"text-2xl md:text-4xl",
|
||||
"text-2xl",
|
||||
"mb-2",
|
||||
"font-semibold",
|
||||
"tracking-tight",
|
||||
@@ -48,7 +48,7 @@ export function H3({ children, className }: HeadingProps) {
|
||||
className,
|
||||
"font-display",
|
||||
"text-stone-950 dark:text-white",
|
||||
"text-xl md:text-2xl",
|
||||
"text-xl",
|
||||
"mb-2",
|
||||
"font-semibold",
|
||||
"tracking-tight",
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
export function P({ children, className }: { children: React.ReactNode, className?: string }) {
|
||||
return <p className={clsx(className, "mb-4")}>{children}</p>;
|
||||
export function P({ children }: { children: React.ReactNode }) {
|
||||
return <p className="mb-4">{children}</p>;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export function Input(props: Props) {
|
||||
const inputClassName = clsx(
|
||||
"w-full rounded-md border px-3.5 py-2 shadow-sm",
|
||||
"font-medium text-stone-900",
|
||||
"dark:text-white",
|
||||
"dark:border-stone-900 dark:text-white",
|
||||
);
|
||||
|
||||
const containerClassName = clsx("grid gap-1", className);
|
||||
|
||||
@@ -5,20 +5,17 @@ export function LabelledFeatureIcon({
|
||||
label,
|
||||
icon: Icon,
|
||||
explanation,
|
||||
className,
|
||||
}: {
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
explanation: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
className,
|
||||
"p-4 flex flex-col gap-3",
|
||||
"not-prose text-base",
|
||||
"border rounded-xl",
|
||||
"border border-stone-200 dark:border-stone-900 rounded-xl",
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
|
||||
@@ -1,32 +1,23 @@
|
||||
import clsx from "clsx";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export function Prose({
|
||||
children,
|
||||
className,
|
||||
size = "md",
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
size?: "sm" | "md" | "lg";
|
||||
}) {
|
||||
const sizeClassName = {
|
||||
sm: "prose-sm",
|
||||
md: "",
|
||||
lg: "prose-xl",
|
||||
}[size];
|
||||
|
||||
export function Prose(props: { children: ReactNode; className?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
className,
|
||||
"prose",
|
||||
sizeClassName,
|
||||
"dark:prose-invert",
|
||||
"prose-code:dark:bg-stone-900",
|
||||
"max-w-4xl prose-stone dark:prose-invert",
|
||||
"prose-headings:font-display",
|
||||
"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: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-stone-50 prose-pre:dark:bg-stone-900 dark:prose-pre:border-stone-800",
|
||||
"prose-pre:text-black dark:prose-pre:text-white",
|
||||
props.className || "prose",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ function H2Sub({ children }: { children: React.ReactNode }) {
|
||||
"text-lg lg:text-xl",
|
||||
"leading-snug",
|
||||
"tracking-tight",
|
||||
"max-w-2xl",
|
||||
"max-w-4xl",
|
||||
"text-stone-700 dark:text-stone-500",
|
||||
)}
|
||||
>
|
||||
@@ -21,14 +21,12 @@ function H2Sub({ children }: { children: React.ReactNode }) {
|
||||
export function SectionHeader({
|
||||
title,
|
||||
slogan,
|
||||
className,
|
||||
}: {
|
||||
title: ReactNode;
|
||||
slogan: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<hgroup className={clsx(className, "mb-5")}>
|
||||
<hgroup className="mb-5">
|
||||
<H2>{title}</H2>
|
||||
<H2Sub>{slogan}</H2Sub>
|
||||
</hgroup>
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
export function Testimonial({
|
||||
children,
|
||||
name,
|
||||
role,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
name: string;
|
||||
role: string;
|
||||
}) {
|
||||
return (
|
||||
<figure className="max-w-2xl">
|
||||
<svg
|
||||
className="size-8 text-blue"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M9.352 4C4.456 7.456 1 13.12 1 19.36c0 5.088 3.072 8.064 6.624 8.064 3.36 0 5.856-2.688 5.856-5.856 0-3.168-2.208-5.472-5.088-5.472-.576 0-1.344.096-1.536.192.48-3.264 3.552-7.104 6.624-9.024L9.352 4zm16.512 0c-4.8 3.456-8.256 9.12-8.256 15.36 0 5.088 3.072 8.064 6.624 8.064 3.264 0 5.856-2.688 5.856-5.856 0-3.168-2.304-5.472-5.184-5.472-.576 0-1.248.096-1.44.192.48-3.264 3.456-7.104 6.528-9.024L25.864 4z"></path>
|
||||
</svg>
|
||||
<blockquote className="mt-6 text-balance font-medium leading-8 text-stone-900 sm:text-lg dark:text-white">
|
||||
{children}
|
||||
</blockquote>
|
||||
<figcaption className="mt-6 flex items-center gap-x-6">
|
||||
<div className="text-sm leading-6 sm:text-base">
|
||||
<div className="font-semibold text-stone-900 dark:text-white">
|
||||
{name}
|
||||
</div>
|
||||
<div className="mt-0.5 text-stone-600 dark:text-stone-500">
|
||||
{role}
|
||||
</div>
|
||||
</div>
|
||||
</figcaption>
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ThemeProvider as NextThemesProvider, useTheme } from "next-themes";
|
||||
import { type ThemeProviderProps } from "next-themes/dist/types";
|
||||
import { useEffect } from "react";
|
||||
|
||||
function ThemeWatcher() {
|
||||
let { resolvedTheme, setTheme } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
let media = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
|
||||
function onMediaChange() {
|
||||
let systemTheme = media.matches ? "dark" : "light";
|
||||
if (resolvedTheme === systemTheme) {
|
||||
setTheme("system");
|
||||
}
|
||||
}
|
||||
|
||||
onMediaChange();
|
||||
media.addEventListener("change", onMediaChange);
|
||||
|
||||
return () => {
|
||||
media.removeEventListener("change", onMediaChange);
|
||||
};
|
||||
}, [resolvedTheme, setTheme]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return (
|
||||
<NextThemesProvider {...props}>
|
||||
<ThemeWatcher />
|
||||
{children}
|
||||
</NextThemesProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { MoonIcon, SunIcon } from "lucide-react";
|
||||
import clsx from "clsx";
|
||||
|
||||
export function ThemeToggle({ className }: { className?: string }) {
|
||||
let { resolvedTheme, setTheme } = useTheme();
|
||||
let otherTheme = resolvedTheme === "dark" ? "light" : "dark";
|
||||
let [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(
|
||||
className,
|
||||
"md:p-2 md:rounded-full md:border",
|
||||
"text-stone-400 hover:text-stone-900 dark:text-stone-400 dark:hover:text-white",
|
||||
"md:hover:bg-stone-200 md:dark:hover:bg-stone-900",
|
||||
"transition-colors"
|
||||
)}
|
||||
aria-label={
|
||||
mounted ? `Switch to ${otherTheme} theme` : "Toggle theme"
|
||||
}
|
||||
onClick={() => setTheme(otherTheme)}
|
||||
>
|
||||
<MoonIcon
|
||||
size={24}
|
||||
strokeWidth={1.5}
|
||||
className="size-5 md:size-6 stroke-stone-900 dark:hidden"
|
||||
/>
|
||||
<SunIcon
|
||||
size={24}
|
||||
strokeWidth={1.5}
|
||||
className="size-5 md:size-6 hidden stroke-white dark:block"
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { ReactNode } from "react";
|
||||
import { ThemeToggle } from "../molecules/ThemeToggle";
|
||||
|
||||
type FooterSection = {
|
||||
title: string;
|
||||
@@ -21,38 +20,20 @@ type FooterProps = {
|
||||
sections: FooterSection[];
|
||||
};
|
||||
|
||||
function Copyright({
|
||||
className,
|
||||
companyName,
|
||||
}: {
|
||||
companyName: string;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<p className={clsx(className, "text-sm")}>
|
||||
© {new Date().getFullYear()} {companyName}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export function Footer({ logo, companyName, sections }: FooterProps) {
|
||||
return (
|
||||
<footer className="w-full border-t bg-stone-100 dark:bg-stone-925">
|
||||
<div className="container py-8 md:py-16 grid gap-y-8 grid-cols-12">
|
||||
<div className="flex flex-col justify-between col-span-full md:col-span-4">
|
||||
<footer className="flex z-10 mt-10 min-h-[15rem] border-t bg-stone-100 dark:bg-stone-925 text-stone-600 dark:text-stone-400 w-full justify-center dark:border-stone-900">
|
||||
<div className="p-8 container w-full grid grid-cols-3 md:grid-cols-4 lg:grid-cols-7 gap-8 max-sm:mb-12">
|
||||
<div className="col-span-full md:col-span-1 sm:row-start-4 md:row-start-auto lg:col-span-2 md:row-span-2 md:flex-1 flex flex-row md:flex-col max-sm:mt-4 justify-between max-sm:items-start gap-2 text-sm min-w-[10rem]">
|
||||
{logo}
|
||||
|
||||
<Copyright
|
||||
className="hidden md:block"
|
||||
companyName={companyName}
|
||||
/>
|
||||
<p className="max-sm:text-right">
|
||||
© {new Date().getFullYear()}
|
||||
<br />
|
||||
{companyName}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{sections.map((section, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col gap-2 text-sm col-span-4 sm:col-span-4 md:col-span-2"
|
||||
>
|
||||
<div key={index} className="flex flex-col gap-2 text-sm">
|
||||
<h2 className="font-medium">{section.title}</h2>
|
||||
{section.links.map((link, linkIndex) => (
|
||||
<FooterLink
|
||||
@@ -65,15 +46,6 @@ export function Footer({ logo, companyName, sections }: FooterProps) {
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="hidden md:flex justify-end items-end md:col-span-2">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
|
||||
<Copyright
|
||||
className="col-span-full md:hidden"
|
||||
companyName={companyName}
|
||||
/>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,6 @@ import { ReactNode, useLayoutEffect, useRef, useState } from "react";
|
||||
import { BreadCrumb } from "../molecules/Breadcrumb";
|
||||
import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
import { ThemeToggle } from "../molecules/ThemeToggle";
|
||||
|
||||
export function Nav({
|
||||
mainLogo,
|
||||
@@ -40,9 +39,11 @@ export function Nav({
|
||||
<nav
|
||||
className={[
|
||||
clsx(
|
||||
"hidden md:flex sticky left-0 right-0 top-0 w-full justify-center",
|
||||
"bg-white dark:bg-stone-950 border-b",
|
||||
"hidden md:flex sticky left-0 right-0 top-0 max-sm:bottom-0 w-full justify-center",
|
||||
"bg-white dark:bg-stone-950 border-b max-sm:border-t border-stone-200 dark:border-stone-900",
|
||||
"max-h-none overflow-hidden transition[max-height] duration-300 ease-in-out",
|
||||
"z-50",
|
||||
menuOpen ? "h-[100dvh]" : "h-16",
|
||||
),
|
||||
].join(" ")}
|
||||
>
|
||||
@@ -89,7 +90,6 @@ export function Nav({
|
||||
setMenuOpen((o) => !o);
|
||||
setSearchOpen(false);
|
||||
}}
|
||||
aria-label="Open menu"
|
||||
>
|
||||
<MenuIcon />
|
||||
<BreadCrumb items={items} />
|
||||
@@ -108,7 +108,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 border 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" : "",
|
||||
)}
|
||||
>
|
||||
@@ -118,7 +118,7 @@ export function Nav({
|
||||
" px-2 pb-2",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center w-full border-b">
|
||||
<div className="flex items-center w-full border-b border-stone-100 dark:border-stone-900">
|
||||
<NavLinkLogo
|
||||
prominent
|
||||
href="/"
|
||||
@@ -141,7 +141,7 @@ export function Nav({
|
||||
</div>
|
||||
|
||||
{pathname.startsWith("/docs") && docNav && (
|
||||
<div className="max-h-[calc(100dvh-15rem)] p-4 border-b overflow-x-auto">
|
||||
<div className="max-h-[calc(100dvh-15rem)] p-4 border-b border-stone-100 dark:border-stone-900 overflow-x-auto">
|
||||
{docNav}
|
||||
</div>
|
||||
)}
|
||||
@@ -162,7 +162,7 @@ export function Nav({
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 justify-end border-b">
|
||||
<div className="flex gap-4 justify-end border-b border-stone-100 dark:border-stone-900">
|
||||
{items
|
||||
.filter((item) => !("icon" in item))
|
||||
.slice(3)
|
||||
@@ -179,12 +179,12 @@ export function Nav({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center self-stretch justify-between">
|
||||
<div className="flex items-center self-stretch justify-end">
|
||||
{/* <input
|
||||
type="text"
|
||||
className={clsx(
|
||||
menuOpen || searchOpen ? "" : "hidden",
|
||||
"ml-2 border px-2 py-1 rounded w-full"
|
||||
"ml-2 border border-stone-200 dark:border-stone-900 px-2 py-1 rounded w-full"
|
||||
)}
|
||||
placeholder="Search docs..."
|
||||
ref={searchRef}
|
||||
@@ -202,16 +202,12 @@ export function Nav({
|
||||
>
|
||||
<SearchIcon className="" />
|
||||
</button> */}
|
||||
{(menuOpen || searchOpen) && (
|
||||
<ThemeToggle className="p-3" />
|
||||
)}
|
||||
<button
|
||||
className="flex gap-2 p-3 rounded-xl items-center"
|
||||
onMouseDown={() => {
|
||||
setMenuOpen((o) => !o);
|
||||
setSearchOpen(false);
|
||||
}}
|
||||
aria-label="Close menu"
|
||||
>
|
||||
{menuOpen || searchOpen ? (
|
||||
<XIcon />
|
||||
|
||||
@@ -1,3 +1,45 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* .manrope-bold {
|
||||
font-family: "Manrope", sans-serif;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
} */
|
||||
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 214, 219, 220;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
|
||||
font-family: "Manrope", sans-serif;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-start-rgb: 0, 0, 0;
|
||||
--background-end-rgb: 0, 0, 0;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,6 @@ export default function RootLayout({
|
||||
commitMono.variable,
|
||||
inter.className,
|
||||
"h-full",
|
||||
"bg-white text-stone-700 dark:text-stone-400 dark:bg-stone-950",
|
||||
].join(" ")}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,72 +1,9 @@
|
||||
import { Prose } from "@components/molecules/Prose";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="container flex flex-col gap-8 py-8 lg:py-16">
|
||||
<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>
|
||||
|
||||
<h2>Typography (Prose)</h2>
|
||||
|
||||
<div className="grid gap-4">
|
||||
<div>
|
||||
Heading 1
|
||||
<Prose className="p-3 border">
|
||||
<h1>Ship top-tier apps at high tempo</h1>
|
||||
</Prose>
|
||||
</div>
|
||||
<div>
|
||||
Heading 2
|
||||
<Prose className="p-3 border">
|
||||
<h2>Ship top-tier apps at high tempo</h2>
|
||||
</Prose>
|
||||
</div>
|
||||
<div>
|
||||
Heading 3
|
||||
<Prose className="p-3 border">
|
||||
<h3>Ship top-tier apps at high tempo</h3>
|
||||
</Prose>
|
||||
</div>
|
||||
<div>
|
||||
Heading 4
|
||||
<Prose className="p-3 border">
|
||||
<h4>Ship top-tier apps at high tempo</h4>
|
||||
</Prose>
|
||||
</div>
|
||||
<div>
|
||||
Paragraph
|
||||
<Prose className="p-3 border">
|
||||
<p>
|
||||
<strong>
|
||||
Jazz is a framework for building local-first
|
||||
apps
|
||||
</strong>{" "}
|
||||
— an architecture that lets companies like Figma and
|
||||
Linear play in a league of their own.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open source. Self-host or use Jazz Cloud for
|
||||
zero-config magic.
|
||||
</p>
|
||||
</Prose>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Link
|
||||
<Prose className="p-3 border">
|
||||
This is a <a href="https://jazz.tools">link</a>
|
||||
</Prose>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Code
|
||||
<Prose className="p-3 border">
|
||||
This is a one-line <code>piece of code</code>
|
||||
</Prose>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,28 +4,6 @@ import tailwindCSSAnimate from "tailwindcss-animate";
|
||||
const colors = require("tailwindcss/colors")
|
||||
const plugin = require("tailwindcss/plugin")
|
||||
|
||||
const stonePalette = {
|
||||
"50": "oklch(0.988281 0.002 75)",
|
||||
"75": "oklch(0.980563 0.002 75)",
|
||||
"100": "oklch(0.964844 0.002 75)",
|
||||
"200": "oklch(0.917969 0.002 75)",
|
||||
"300": "oklch(0.853516 0.002 75)",
|
||||
"400": "oklch(0.789063 0.002 75)",
|
||||
"500": "oklch(0.726563 0.002 75)",
|
||||
"600": "oklch(0.613281 0.002 75)",
|
||||
"700": "oklch(0.523438 0.002 75)",
|
||||
"800": "oklch(0.412109 0.002 75)",
|
||||
"900": "oklch(0.302734 0.002 75)",
|
||||
"925": "oklch(0.220000 0.002 75)",
|
||||
"950": "oklch(0.193359 0.002 75)",
|
||||
}
|
||||
|
||||
const stonePaletteWithAlpha = {...stonePalette};
|
||||
|
||||
Object.keys(stonePalette).forEach(key => {
|
||||
stonePaletteWithAlpha[key] = stonePaletteWithAlpha[key].replace(")", "/ <alpha-value>)")
|
||||
})
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
const config = {
|
||||
content: [
|
||||
@@ -36,7 +14,21 @@ const config = {
|
||||
theme: {
|
||||
colors: {
|
||||
...harmonyPalette,
|
||||
stone: stonePaletteWithAlpha,
|
||||
stone: {
|
||||
"50": "oklch(0.988281 0.002 75 / <alpha-value>)",
|
||||
"75": "oklch(0.980563 0.002 75 / <alpha-value>)",
|
||||
"100": "oklch(0.964844 0.002 75 / <alpha-value>)",
|
||||
"200": "oklch(0.917969 0.002 75 / <alpha-value>)",
|
||||
"300": "oklch(0.853516 0.002 75 / <alpha-value>)",
|
||||
"400": "oklch(0.789063 0.002 75 / <alpha-value>)",
|
||||
"500": "oklch(0.726563 0.002 75 / <alpha-value>)",
|
||||
"600": "oklch(0.613281 0.002 75 / <alpha-value>)",
|
||||
"700": "oklch(0.523438 0.002 75 / <alpha-value>)",
|
||||
"800": "oklch(0.412109 0.002 75 / <alpha-value>)",
|
||||
"900": "oklch(0.302734 0.002 75 / <alpha-value>)",
|
||||
"925": "oklch(0.220000 0.002 75 / <alpha-value>)",
|
||||
"950": "oklch(0.193359 0.002 75 / <alpha-value>)",
|
||||
},
|
||||
blue: {
|
||||
...colors.indigo,
|
||||
"500": "#5870F1",
|
||||
@@ -46,15 +38,15 @@ const config = {
|
||||
"900": "#12046A",
|
||||
DEFAULT: "#3313F7",
|
||||
},
|
||||
green: colors.green,
|
||||
red: colors.red,
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
display: ["var(--font-manrope)"],
|
||||
mono: ["var(--font-commit-mono)"],
|
||||
handwritten: ["var(--font-sriracha)"],
|
||||
},
|
||||
fontSize: {
|
||||
|
||||
'2xs': ['0.75rem', { lineHeight: '1.25rem' }],
|
||||
},
|
||||
// shadcn-ui
|
||||
@@ -93,6 +85,11 @@ const config = {
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
@@ -122,107 +119,12 @@ const config = {
|
||||
md: "960px",
|
||||
lg: "1276px",
|
||||
},
|
||||
typography: (theme) => ({
|
||||
DEFAULT: {
|
||||
css: {
|
||||
"--tw-prose-body": stonePalette[700],
|
||||
"--tw-prose-headings": stonePalette[900],
|
||||
"--tw-prose-bold": stonePalette[900],
|
||||
"--tw-prose-invert-bold": theme("colors.white"),
|
||||
"--tw-prose-invert-body": stonePalette[400],
|
||||
"--tw-prose-invert-headings": theme("colors.white"),
|
||||
"--tw-prose-code": stonePalette[900],
|
||||
"--tw-prose-invert-code": stonePalette[50],
|
||||
"--tw-prose-links": theme("colors.blue.DEFAULT"),
|
||||
"--tw-prose-invert-links": theme("colors.blue.500"),
|
||||
maxWidth: theme("screens.4xl"),
|
||||
strong: {
|
||||
color: "var(--tw-prose-bold)",
|
||||
fontWeight: theme("fontWeight.medium"),
|
||||
},
|
||||
b: {
|
||||
color: "var(--tw-prose-bold)",
|
||||
fontWeight: theme("fontWeight.medium"),
|
||||
},
|
||||
a: {
|
||||
fontWeight: theme("fontWeight.normal"),
|
||||
textUnderlineOffset: "4px",
|
||||
},
|
||||
h1: {
|
||||
fontFamily: theme("fontFamily.display"),
|
||||
letterSpacing: theme("letterSpacing.tight"),
|
||||
fontWeight: theme("fontWeight.semibold"),
|
||||
fontSize: theme("fontSize.4xl"),
|
||||
},
|
||||
h2: {
|
||||
fontFamily: theme("fontFamily.display"),
|
||||
letterSpacing: theme("letterSpacing.tight"),
|
||||
fontWeight: theme("fontWeight.semibold"),
|
||||
fontSize: theme("fontSize.3xl"),
|
||||
},
|
||||
h3: {
|
||||
fontFamily: theme("fontFamily.display"),
|
||||
letterSpacing: theme("letterSpacing.tight"),
|
||||
fontWeight: theme("fontWeight.semibold"),
|
||||
fontSize: theme("fontSize.2xl"),
|
||||
},
|
||||
h4: {
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: theme("letterSpacing.widest"),
|
||||
fontWeight: theme("fontWeight.medium"),
|
||||
fontSize: theme("fontSize.sm"),
|
||||
},
|
||||
'code::before': {
|
||||
content: 'none',
|
||||
},
|
||||
'code::after': {
|
||||
content: 'none',
|
||||
},
|
||||
code: {
|
||||
backgroundColor: stonePalette[100],
|
||||
padding: "0.15rem 0.25rem",
|
||||
borderRadius: "2px",
|
||||
whiteSpace: "nowrap",
|
||||
},
|
||||
p: {
|
||||
marginBottom: theme("spacing.3"),
|
||||
marginTop: theme("spacing.3"),
|
||||
}
|
||||
}
|
||||
},
|
||||
xl: {
|
||||
css: {
|
||||
p: {
|
||||
marginBottom: theme("spacing.3"),
|
||||
marginTop: theme("spacing.3"),
|
||||
}
|
||||
},
|
||||
}
|
||||
}),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
tailwindCSSAnimate,
|
||||
typography(),
|
||||
plugin(({ addVariant }) => addVariant("label", "& :is(label)")),
|
||||
plugin(({ addUtilities }) => addUtilities({
|
||||
".text-reset, .text-reset:hover, .text-reset:focus": {
|
||||
color: "inherit",
|
||||
textDecoration: "none",
|
||||
},
|
||||
})),
|
||||
plugin(({ addBase }) => addBase({
|
||||
":root": {
|
||||
"--gcmp-border-color": stonePalette[200],
|
||||
"--gcmp-invert-border-color": stonePalette[900],
|
||||
},
|
||||
"*": {
|
||||
borderColor: "var(--gcmp-border-color)",
|
||||
},
|
||||
".dark *": {
|
||||
borderColor: "var(--gcmp-invert-border-color)",
|
||||
},
|
||||
}))
|
||||
],
|
||||
};
|
||||
export default config;
|
||||
|
||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 15 KiB |
@@ -65,6 +65,10 @@
|
||||
--ring: 24 5.7% 82.9%;
|
||||
}
|
||||
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
.overlay-close {
|
||||
@apply bg-black;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import "./globals.css";
|
||||
import type { Metadata } from "next";
|
||||
import { ThemeProvider } from "gcmp-design-system/src/app/components/molecules/ThemeProvider";
|
||||
|
||||
import { ThemeProvider } from "@/components/themeProvider";
|
||||
import { Inter, Manrope } from "next/font/google";
|
||||
import localFont from "next/font/local";
|
||||
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
import { GcmpNav } from "@/components/Nav";
|
||||
import { ThemeToggle } from "gcmp-design-system/src/app/components/molecules/ThemeToggle";
|
||||
|
||||
// If loading a variable font, you don't need to specify the font weight
|
||||
const manrope = Manrope({
|
||||
@@ -23,23 +19,6 @@ const inter = Inter({
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const commitMono = localFont({
|
||||
src: [
|
||||
{
|
||||
path: "../../design-system/fonts/CommitMono-Regular.woff2",
|
||||
weight: "400",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../design-system/fonts/CommitMono-Regular.woff",
|
||||
weight: "400",
|
||||
style: "normal",
|
||||
},
|
||||
],
|
||||
variable: "--font-commit-mono",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const metaTags = {
|
||||
title: "garden computing",
|
||||
description:
|
||||
@@ -77,13 +56,12 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className="h-full">
|
||||
<html lang="en">
|
||||
<body
|
||||
className={[
|
||||
manrope.variable,
|
||||
commitMono.variable,
|
||||
inter.className,
|
||||
"h-full flex flex-col items-center",
|
||||
"flex min-h-screen flex-col items-center",
|
||||
"bg-white text-stone-700 dark:text-stone-400 dark:bg-stone-950",
|
||||
].join(" ")}
|
||||
>
|
||||
@@ -99,10 +77,8 @@ export default function RootLayout({
|
||||
<main className="flex flex-1 flex-col w-full">
|
||||
{children}
|
||||
</main>
|
||||
<footer className="py-8 md:py-16 text-sm flex justify-between gap-3 w-full container">
|
||||
<footer className="py-8 md:py-16 text-sm">
|
||||
<p>©2024 Garden Computing, Inc.</p>
|
||||
|
||||
<ThemeToggle className="hidden md:block"/>
|
||||
</footer>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getPostBySlug, posts } from "@/lib/posts";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { H1 } from "gcmp-design-system/src/app/components/atoms/Headings";
|
||||
import PostCoverImage from "@/components/blog/PostCoverImage";
|
||||
import Image from "next/image";
|
||||
import { FormattedDate } from "@/components/FormattedDate";
|
||||
import { PostJsonLd } from "@/components/blog/PostJsonLd";
|
||||
|
||||
export default async function Post({ params }: Params) {
|
||||
const post = getPostBySlug(params.slug);
|
||||
|
||||
if (!post) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const { title, coverImage, date, author, excerpt } = post.meta;
|
||||
const content = post.default({});
|
||||
|
||||
return (
|
||||
<>
|
||||
<PostJsonLd
|
||||
title={title}
|
||||
image={coverImage}
|
||||
author={author.name}
|
||||
datePublished={date}
|
||||
description={excerpt}
|
||||
/>
|
||||
<article className="container max-w-3xl flex flex-col gap-8 py-8 lg:py-16 lg:gap-12">
|
||||
<div className="flex flex-col gap-2">
|
||||
<H1>{title}</H1>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Image
|
||||
width={100}
|
||||
height={100}
|
||||
src={author.image}
|
||||
className="size-12 rounded-full"
|
||||
alt=""
|
||||
/>
|
||||
<div>
|
||||
<p className="text-stone-900 dark:text-white">
|
||||
{author.name}
|
||||
</p>
|
||||
<p className="text-sm text-stone-600 dark:text-stone-400">
|
||||
<FormattedDate date={date} />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PostCoverImage src={coverImage} title={title} />
|
||||
|
||||
<Prose>{content}</Prose>
|
||||
</article>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type Params = {
|
||||
params: {
|
||||
slug: string;
|
||||
};
|
||||
};
|
||||
|
||||
export function generateMetadata({ params }: Params): Metadata {
|
||||
const post = getPostBySlug(params.slug);
|
||||
|
||||
if (!post) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const { title, excerpt, coverImage } = post.meta;
|
||||
|
||||
return {
|
||||
title,
|
||||
description: excerpt,
|
||||
openGraph: {
|
||||
title,
|
||||
images: [coverImage],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return posts.map((post) => ({
|
||||
slug: post.meta.slug,
|
||||
}));
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import Link from "next/link";
|
||||
import { HeroHeader } from "gcmp-design-system/src/app/components/molecules/HeroHeader";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { NewsletterForm } from "@/components/NewsletterForm";
|
||||
import { Posts } from "@/components/blog/Posts";
|
||||
|
||||
export const metadata = {
|
||||
title: "Blog",
|
||||
@@ -34,8 +33,6 @@ export default function NewsPage() {
|
||||
!
|
||||
</p>
|
||||
</Prose>
|
||||
|
||||
<Posts />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ pre.twoslash data-lsp::before {
|
||||
position: absolute;
|
||||
transform: translate(0, 1.2rem);
|
||||
max-width: 30rem;
|
||||
@apply text-xs px-1.5 py-1 rounded border shadow-lg overflow-hidden whitespace-pre-wrap text-stone-700 bg-stone-50 dark:text-stone-200 dark:bg-stone-950;
|
||||
@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;
|
||||
@@ -79,7 +79,7 @@ pre.twoslash data-lsp:hover::before {
|
||||
}
|
||||
|
||||
pre .code-container {
|
||||
@apply overflow-auto p-2 pl-0 bg-white dark:bg-stone-925 rounded-b-xl text-xs h-full;
|
||||
@apply overflow-auto p-2 pl-0 bg-white dark:bg-stone-925 rounded-b-xl text-xs h-full dark:border-stone-900;
|
||||
}
|
||||
/* The try button */
|
||||
pre .code-container > a {
|
||||
|
||||
@@ -1,187 +1,42 @@
|
||||
import { GlobeIcon, LucideIcon } from "lucide-react";
|
||||
import { H1, H2 } from "gcmp-design-system/src/app/components/atoms/Headings";
|
||||
import { MailIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
SiBluesky,
|
||||
SiGithub,
|
||||
SiGitlab,
|
||||
SiLinkedin,
|
||||
SiX,
|
||||
} from "@icons-pack/react-simple-icons";
|
||||
import { SiGithub, SiTwitter } from "@icons-pack/react-simple-icons";
|
||||
import { SectionHeader } from "gcmp-design-system/src/app/components/molecules/SectionHeader";
|
||||
import { HeroHeader } from "gcmp-design-system/src/app/components/molecules/HeroHeader";
|
||||
|
||||
interface TeamMember {
|
||||
name: string;
|
||||
titles: string[];
|
||||
image?: string;
|
||||
location: string;
|
||||
x?: string;
|
||||
github?: string;
|
||||
gitlab?: string;
|
||||
website?: string;
|
||||
linkedin?: string;
|
||||
bluesky?: string;
|
||||
}
|
||||
|
||||
const team: Array<TeamMember> = [
|
||||
{
|
||||
name: "Anselm Eickhoff",
|
||||
titles: ["Founder"],
|
||||
image: "anselm.jpg",
|
||||
location: "Canterbury, UK ",
|
||||
x: "anselm_io",
|
||||
github: "aeplay",
|
||||
website: "http://anselm.io",
|
||||
bluesky: "anselm-io",
|
||||
},
|
||||
{
|
||||
name: "Andrei Popa",
|
||||
titles: ["Full-Stack Dev", "Infra"],
|
||||
image: "andrei.jpeg",
|
||||
location: "Bucharest, Romania ",
|
||||
x: "elitepax",
|
||||
github: "pax-k",
|
||||
},
|
||||
{
|
||||
name: "Guido D'Orsi",
|
||||
titles: ["Frontend Dev", "React Performance"],
|
||||
image: "guido.jpeg",
|
||||
location: "Piano di Sorrento, Italy ",
|
||||
github: "gdorsi",
|
||||
},
|
||||
{
|
||||
name: "Trisha Lim",
|
||||
titles: ["Frontend Dev", "Design", "Marketing"],
|
||||
image: "trisha.png",
|
||||
location: "Lisbon, Portugal ",
|
||||
github: "trishalim",
|
||||
website: "https://trishalim.com",
|
||||
},
|
||||
{
|
||||
name: "Benjamin Leveritt",
|
||||
titles: ["Full-Stack Dev"],
|
||||
image: "benjamin.jpg",
|
||||
location: "Portsmouth, UK ",
|
||||
github: "bensleveritt",
|
||||
},
|
||||
{
|
||||
name: "Marina Orlova",
|
||||
titles: ["Full-Stack Dev"],
|
||||
location: "Tarragona, Spain ",
|
||||
gitlab: "marinaorlova",
|
||||
linkedin: "marina-orlova-52a34394",
|
||||
// github: "marinoska",
|
||||
image: "marina.jpeg",
|
||||
},
|
||||
];
|
||||
|
||||
function SocialLink({
|
||||
link,
|
||||
label,
|
||||
icon: Icon,
|
||||
}: {
|
||||
label: string;
|
||||
link: string;
|
||||
icon: LucideIcon;
|
||||
}) {
|
||||
return (
|
||||
<Link href={link} className="p-1 -m-1">
|
||||
<Icon size={16} />
|
||||
<span className="sr-only">{label}</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function Person({ person }: { person: TeamMember }) {
|
||||
const imageClassName = "size-24 shadow rounded-md bg-stone-100 sm:size-28 ";
|
||||
return (
|
||||
<div className="flex items-center gap-5">
|
||||
{person.image ? (
|
||||
<img src={`/team/${person.image}`} className={imageClassName} />
|
||||
) : (
|
||||
<span className={imageClassName}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-full pt-5 h-full text-stone-300 dark:text-stone-700"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M4 22a8 8 0 1 1 16 0zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6s6 2.685 6 6s-2.685 6-6 6"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
)}
|
||||
<div className="flex flex-col gap-2.5">
|
||||
<h3 className="text-lg leading-none font-semibold tracking-tight text-stone-900 dark:text-white">
|
||||
{person.name}
|
||||
</h3>
|
||||
<p className="text-sm leading-none text-gray-600 dark:text-stone-400">
|
||||
{person.titles.join(", ")}
|
||||
</p>
|
||||
<p className="text-sm leading-none text-gray-600 dark:text-stone-400">
|
||||
{person.location}
|
||||
</p>
|
||||
|
||||
<div className="flex gap-2 mt-0.5">
|
||||
{person.website && (
|
||||
<SocialLink
|
||||
link={person.website}
|
||||
icon={GlobeIcon}
|
||||
label="Website"
|
||||
/>
|
||||
)}
|
||||
{person.x && (
|
||||
<SocialLink
|
||||
link={`https://x.com/${person.x}`}
|
||||
icon={SiX}
|
||||
label="X profile"
|
||||
/>
|
||||
)}
|
||||
{person.bluesky && (
|
||||
<SocialLink
|
||||
link={`https://bsky.app/profile/${person.bluesky}.bsky.social`}
|
||||
icon={SiBluesky}
|
||||
label="Bluesky profile"
|
||||
/>
|
||||
)}
|
||||
{person.gitlab && (
|
||||
<SocialLink
|
||||
link={`https://gitlab.com/${person.gitlab}`}
|
||||
icon={SiGitlab}
|
||||
label="Gitlab profile"
|
||||
/>
|
||||
)}
|
||||
{person.linkedin && (
|
||||
<SocialLink
|
||||
link={`https://www.linkedin.com/in/${person.linkedin}`}
|
||||
icon={SiLinkedin}
|
||||
label="Linkedin profile"
|
||||
/>
|
||||
)}
|
||||
{person.github && (
|
||||
<SocialLink
|
||||
link={`https://github.com/${person.github}`}
|
||||
label="Github profile"
|
||||
icon={SiGithub}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export const metadata = {
|
||||
title: "Team",
|
||||
};
|
||||
|
||||
export default function TeamPage() {
|
||||
return (
|
||||
<div className="container">
|
||||
<HeroHeader title="Meet the team" slogan="" />
|
||||
<HeroHeader title="Team" slogan="" />
|
||||
|
||||
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3 lg:gap-10">
|
||||
{team.map((person) => (
|
||||
<Person key={person.name} person={person} />
|
||||
))}
|
||||
<div className="grid gap-1">
|
||||
<H2>Anselm Eickhoff (Founder)</H2>
|
||||
<Link
|
||||
href="mailto:anselm@gcmp.io"
|
||||
className="flex gap-1 items-center"
|
||||
aria-label="Email address"
|
||||
>
|
||||
<MailIcon height="1em" /> anselm@gcmp.io
|
||||
</Link>
|
||||
<Link
|
||||
href="https://x.com/anselm_io"
|
||||
className="flex gap-1 items-center"
|
||||
aria-label="Twitter link"
|
||||
>
|
||||
<SiTwitter height="1em" /> anselm_io
|
||||
</Link>
|
||||
<Link
|
||||
href="https://github.com/aeplay"
|
||||
className="flex gap-1 items-center"
|
||||
aria-label="GitHub link"
|
||||
>
|
||||
<SiGithub height="1em" /> aeplay
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { formatDate } from "@/lib/date";
|
||||
|
||||
export function FormattedDate({ date }: { date: string }) {
|
||||
const formattedDate = formatDate(new Date(date));
|
||||
|
||||
return <>{formattedDate}</>;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
const PostCoverImage = ({
|
||||
src,
|
||||
title,
|
||||
slug,
|
||||
alt = "",
|
||||
className,
|
||||
}: {
|
||||
src: string;
|
||||
title: string;
|
||||
slug?: string;
|
||||
alt?: string;
|
||||
className?: string;
|
||||
}) => {
|
||||
const image = (
|
||||
<Image
|
||||
alt={alt}
|
||||
src={src}
|
||||
className={clsx(className, "w-full")}
|
||||
width={1300}
|
||||
height={630}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<div className={className}>
|
||||
{slug ? (
|
||||
<Link
|
||||
className={className}
|
||||
href={`/news/${slug}`}
|
||||
aria-label={title}
|
||||
>
|
||||
{image}
|
||||
</Link>
|
||||
) : (
|
||||
image
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostCoverImage;
|
||||
@@ -1,37 +0,0 @@
|
||||
export function PostJsonLd({
|
||||
title,
|
||||
image,
|
||||
author,
|
||||
datePublished,
|
||||
description,
|
||||
}: {
|
||||
title: string;
|
||||
image: string;
|
||||
author: string;
|
||||
datePublished: string;
|
||||
description: string;
|
||||
}) {
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
headline: title,
|
||||
image,
|
||||
author: {
|
||||
"@type": "Person",
|
||||
name: author,
|
||||
},
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Garden Computing",
|
||||
},
|
||||
datePublished,
|
||||
description,
|
||||
};
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { posts } from "@/lib/posts";
|
||||
import { FormattedDate } from "@/components/FormattedDate";
|
||||
import PostCoverImage from "@/components/blog/PostCoverImage";
|
||||
import Link from "next/link";
|
||||
|
||||
export function Posts() {
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-8">
|
||||
{posts.map((post) => (
|
||||
<div className="flex flex-col gap-2" key={post.meta.slug}>
|
||||
<PostCoverImage
|
||||
src={post.meta.coverImage}
|
||||
title={post.meta.title}
|
||||
slug={post.meta.slug}
|
||||
className="mb-1.5 rounded-lg"
|
||||
/>
|
||||
<Link
|
||||
href={`/news/${post.meta.slug}`}
|
||||
className="text-lg font-medium text-display text-stone-900 dark:text-white"
|
||||
>
|
||||
{post.meta.title}
|
||||
</Link>
|
||||
<p className="line-clamp-3 leading-relaxed text-ellipsis text-sm text-stone-900 dark:text-stone-400">
|
||||
{post.meta.excerpt}
|
||||
</p>
|
||||
<div className="flex text-sm items-center">
|
||||
{post.meta.author.name} •{" "}
|
||||
<FormattedDate date={post.meta.date} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
export const meta = {
|
||||
slug: "test",
|
||||
title: "Test",
|
||||
date: "2023-01-01",
|
||||
coverImage: "/social-image.png",
|
||||
author: {
|
||||
name: "Anselm Eickhoff",
|
||||
picture: "/social-image.png",
|
||||
},
|
||||
excerpt:
|
||||
"Jazz is a framework for local-first data/permissions. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut gravida vel urna sit amet lacinia. Morbi euismod mi ac lacus feugiat, vel sollicitudin urna faucibus. ",
|
||||
};
|
||||
|
||||
Sed viverra in justo sit amet imperdiet. Proin posuere euismod ante, eu bibendum arcu lobortis non. Ut vitae placerat tellus. Phasellus quis tortor sollicitudin nisi scelerisque bibendum nec ac massa. Donec hendrerit felis non est elementum, at rutrum lorem sodales. Nam ligula urna, varius eu sem non, mollis dictum leo. Duis facilisis id sapien et feugiat.
|
||||
|
||||
Proin ultrices sit amet magna et euismod. Duis sed porta ipsum, eu iaculis eros. Proin at purus sollicitudin, consequat velit vel, fermentum turpis. Integer ut dapibus augue. Ut ultrices viverra justo a lobortis. In mattis leo vel condimentum facilisis. Nullam eu nulla et dolor cursus laoreet. Aenean vehicula sollicitudin nibh, placerat semper lorem finibus eu. Curabitur commodo ligula eu metus porta, id facilisis purus mollis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
|
||||
|
||||
Maecenas turpis elit, vulputate at bibendum ut, scelerisque eu arcu. Ut porta dolor elit, eget venenatis arcu pharetra at. Duis facilisis faucibus luctus. Morbi at mi turpis. Aliquam euismod semper purus sed dignissim. Aenean ultricies sollicitudin nisl, in dapibus libero malesuada nec. Quisque pulvinar enim risus, et egestas elit congue quis. Nulla gravida nibh vitae neque malesuada molestie.
|
||||
|
||||
## Heading 2
|
||||
|
||||
Sed viverra in justo sit amet imperdiet. Proin posuere euismod ante, eu bibendum arcu lobortis non. Ut vitae placerat tellus. Phasellus quis tortor sollicitudin nisi scelerisque bibendum nec ac massa. Donec hendrerit felis non est elementum, at rutrum lorem sodales. Nam ligula urna, varius eu sem non, mollis dictum leo. Duis facilisis id sapien et feugiat.
|
||||
9
homepage/gcmp/components/themeProvider.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||
import { type ThemeProviderProps } from "next-themes/dist/types";
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export function formatDate(date: Date) {
|
||||
const day = date.getDate().toString();
|
||||
date.getMonth();
|
||||
const year = date.getFullYear();
|
||||
const month = date.toLocaleString("en", { month: "short" });
|
||||
|
||||
return `${month} ${day}, ${year}`;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import * as TestPost from "@/components/blog/posts/test.mdx";
|
||||
|
||||
export const posts: (typeof TestPost)[] = [];
|
||||
|
||||
export const getPostBySlug = (slug: string) => {
|
||||
return posts.find((post) => post.meta.slug === slug);
|
||||
};
|
||||
13
homepage/gcmp/mdx.d.ts
vendored
@@ -1,13 +0,0 @@
|
||||
declare module "*.mdx" {
|
||||
export const meta: {
|
||||
slug: string;
|
||||
title: string;
|
||||
date: string;
|
||||
coverImage: string;
|
||||
author: {
|
||||
name: string;
|
||||
image: string;
|
||||
};
|
||||
excerpt: string;
|
||||
};
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
"mdast-util-mdx": "^3.0.0",
|
||||
"micromark-extension-mdxjs": "^3.0.0",
|
||||
"next": "14.2.15",
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"resend": "^4.0.0",
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 186 KiB |
@@ -22,12 +22,6 @@
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"mdx.d.ts",
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
6
homepage/homepage/app/(home)/coValuesIntro.mdx
Normal file
@@ -0,0 +1,6 @@
|
||||
Based on CRDTs and public-key cryptography, CoValues...
|
||||
|
||||
- Can be read & edited like simple local JSON state
|
||||
- Can be created anywhere, are automatically synced & persisted
|
||||
- Always keep full edit history & author metadata
|
||||
- Automatically resolve most conflicts
|
||||
@@ -1,9 +1,11 @@
|
||||
import { CodeExampleTabs, ResponsiveIframe } from "@/components/forMdx";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import {
|
||||
Prose,
|
||||
SmallProse,
|
||||
} from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { SectionHeader } from "gcmp-design-system/src/app/components/molecules/SectionHeader";
|
||||
import { GappedGrid } from "gcmp-design-system/src/app/components/molecules/GappedGrid";
|
||||
import { LabelledFeatureIcon } from "gcmp-design-system/src/app/components/molecules/LabelledFeatureIcon";
|
||||
import { Testimonial } from "gcmp-design-system/src/app/components/molecules/Testimonial";
|
||||
import { GridCard } from "gcmp-design-system/src/app/components/atoms/GridCard";
|
||||
import { CodeRef } from "gcmp-design-system/src/app/components/atoms/CodeRef";
|
||||
import { ComingSoonBadge } from "gcmp-design-system/src/app/components/atoms/ComingSoonBadge";
|
||||
@@ -18,7 +20,6 @@ import {
|
||||
HardDriveDownloadIcon,
|
||||
KeyRoundIcon,
|
||||
MousePointerSquareDashedIcon,
|
||||
UserIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
import {
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
Ui_tsx,
|
||||
} from "@/codeSamples/examples/chat/src";
|
||||
|
||||
import CoValuesIntro from "./coValuesIntro.mdx";
|
||||
import CoMapDescription from "./coValueDescriptions/coMapDescription.mdx";
|
||||
import CoListDescription from "./coValueDescriptions/coListDescription.mdx";
|
||||
import CoPlainTextDescription from "./coValueDescriptions/coPlainTextDescription.mdx";
|
||||
@@ -39,37 +41,67 @@ import GroupDescription from "./coValueDescriptions/groupDescription.mdx";
|
||||
import AccountDescription from "./coValueDescriptions/accountDescription.mdx";
|
||||
import AutoSubDescription from "./toolkit/autoSub.mdx";
|
||||
import CursorsAndCaretsDescription from "./toolkit/cursorsAndCarets.mdx";
|
||||
import AuthProvidersDescription from "./toolkit/authProviders.mdx";
|
||||
import TwoWaySyncDescription from "./toolkit/twoWaySync.mdx";
|
||||
import FileUploadDownloadDescription from "./toolkit/fileUploadDownload.mdx";
|
||||
import VideoPresenceCallsDescription from "./toolkit/videoPresenceCalls.mdx";
|
||||
import CloudIntro from "./cloudIntro.mdx";
|
||||
import { H2, H3 } from "gcmp-design-system/src/app/components/atoms/Headings";
|
||||
import { H3 } from "gcmp-design-system/src/app/components/atoms/Headings";
|
||||
import Link from "next/link";
|
||||
import { DiagramBeforeJazz } from "@/components/DiagramBeforeJazz";
|
||||
import { DiagramAfterJazz } from "@/components/DiagramAfterJazz";
|
||||
import { SupportedEnvironments } from "@/components/home/SupportedEnvironments";
|
||||
import { HairlineBleedGrid } from "gcmp-design-system/src/app/components/molecules/HairlineGrid";
|
||||
import { Hero } from "@/components/home/Hero";
|
||||
import { HowItWorks } from "@/components/home/HowItWorks";
|
||||
import BeforeAfterJazz from "@/components/home/BeforeAfterJazz";
|
||||
import { P } from "gcmp-design-system/src/app/components/atoms/Paragraph";
|
||||
|
||||
const ArrowDoodle = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="107"
|
||||
height="85"
|
||||
viewBox="0 0 107 85"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M3.87338 33.7809C32.7867 45.4747 65.5543 47.9975 91.7667 37.4141"
|
||||
stroke="currentColor"
|
||||
strokeWidth="6"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M74.1719 24.958C83.1201 33.0289 92.3253 37.1887 98.5899 34.6593"
|
||||
stroke="currentColor"
|
||||
strokeWidth="6"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M87.2448 58.8003C88.3842 46.6564 92.3253 37.1887 98.5899 34.6593"
|
||||
stroke="currentColor"
|
||||
strokeWidth="6"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default function Home() {
|
||||
const localFirst = {
|
||||
title: "Local-first",
|
||||
icon: HardDriveDownloadIcon,
|
||||
description: (
|
||||
<>
|
||||
<p>
|
||||
All data you load or create is persisted locally, so your
|
||||
users can work offline.
|
||||
</p>
|
||||
<p>
|
||||
When you’re back online, your local changes are synced to
|
||||
the server.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: "Local-first",
|
||||
icon: HardDriveDownloadIcon,
|
||||
description: (
|
||||
<>
|
||||
<p>
|
||||
All data you load or create is persisted locally, so
|
||||
your users can work offline.
|
||||
</p>
|
||||
<p>
|
||||
When you’re back online, your local changes are synced
|
||||
to the server.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Multiplayer",
|
||||
icon: MousePointerSquareDashedIcon,
|
||||
@@ -138,7 +170,7 @@ export default function Home() {
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Real-time sync",
|
||||
title: "Sync",
|
||||
icon: MonitorSmartphoneIcon,
|
||||
description: (
|
||||
<>
|
||||
@@ -154,7 +186,7 @@ export default function Home() {
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "E2E encryption",
|
||||
title: "E2EE",
|
||||
icon: KeyRoundIcon,
|
||||
description: (
|
||||
<>
|
||||
@@ -185,64 +217,113 @@ export default function Home() {
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Authentication",
|
||||
icon: UserIcon,
|
||||
description: (
|
||||
<>
|
||||
<p>Plug and play different kinds of auth.</p>
|
||||
<ul className="pl-4 list-disc">
|
||||
<li>DemoAuth (for quick multi-user demos)</li>
|
||||
<li>WebAuthN (TouchID/FaceID)</li>
|
||||
<li>Clerk</li>
|
||||
<li>
|
||||
Auth0, Okta, NextAuth <ComingSoonBadge />
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Hero features={features} />
|
||||
|
||||
<BeforeAfterJazz />
|
||||
<div className="bg-stone-100 border-y dark:bg-stone-925 py-8 lg:py-16 dark:border-stone-900">
|
||||
<div className="container grid gap-8 lg:gap-12">
|
||||
<h2 className="font-display md:text-center text-stone-950 dark:text-white text-2xl md:text-3xl font-semibold tracking-tight">
|
||||
Hard things are easy now.
|
||||
</h2>
|
||||
<div className="grid gap-8 md:grid-cols-11 lg:gap-5">
|
||||
<div className="md:col-span-5 flex flex-col justify-between">
|
||||
<div className="text-pretty leading-relaxed text-lg max-w-2xl space-y-2 md:text-xl md:space-y-4 md:leading-relaxed">
|
||||
<p>
|
||||
Ever notice how every stack just{" "}
|
||||
<span className="font-semibold text-stone-900 dark:text-white">
|
||||
reinvents shared state between users and
|
||||
machines
|
||||
</span>
|
||||
?
|
||||
</p>
|
||||
<p>
|
||||
And far from the simple client-server model,
|
||||
you routinely tackle{" "}
|
||||
<span className="font-semibold text-stone-900 dark:text-white">
|
||||
a mess of moving parts
|
||||
</span>
|
||||
, tech choices and deployment questions.
|
||||
</p>
|
||||
<p>
|
||||
And{" "}
|
||||
<span className="font-semibold text-stone-900 dark:text-white">
|
||||
your app’s code is all over the place.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full mt-8 lg:mt-12">
|
||||
<div className="p-4 sm:p-8 rounded-xl bg-white shadow-sm dark:bg-stone-900">
|
||||
<DiagramBeforeJazz className="w-full h-auto max-w-[30rem] mx-auto dark:text-white"></DiagramBeforeJazz>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container flex flex-col gap-12 py-12 lg:gap-20 lg:py-20">
|
||||
<HowItWorks />
|
||||
<div className="hidden md:block relative pr-3 top-24">
|
||||
<ArrowDoodle className="w-full h-auto text-stone-300 dark:text-stone-800" />
|
||||
</div>
|
||||
|
||||
<Testimonial name="Serious Adopter #4" role="Technical Founder">
|
||||
<p>
|
||||
You don't have to think about deploying a database,
|
||||
SQL schemas, relations, and writing queries… Basically,{" "}
|
||||
<span className="bg-blue-50 px-1 dark:bg-transparent">
|
||||
if you know TypeScript, you know Jazz
|
||||
</span>
|
||||
, and you can ship an app. It's just so nice!
|
||||
</p>
|
||||
</Testimonial>
|
||||
<div className="md:col-span-5 flex flex-col justify-between">
|
||||
<div className="text-pretty leading-relaxed text-lg max-w-2xl space-y-2 md:text-xl md:space-y-4 md:leading-relaxed">
|
||||
<p>
|
||||
Jazz provides a single new abstraction to do
|
||||
the whole job.
|
||||
</p>
|
||||
<p>
|
||||
It turns the data flow around and gives you{" "}
|
||||
<span className="font-semibold text-stone-900 dark:text-white">
|
||||
mutable local state
|
||||
</span>
|
||||
, solving{" "}
|
||||
<span className="font-semibold text-stone-900 dark:text-white">
|
||||
sync
|
||||
</span>
|
||||
,{" "}
|
||||
<span className="font-semibold text-stone-900 dark:text-white">
|
||||
concurrent editing
|
||||
</span>{" "}
|
||||
and{" "}
|
||||
<span className="font-semibold text-stone-900 dark:text-white">
|
||||
permissions
|
||||
</span>{" "}
|
||||
under the hood.
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-semibold text-stone-900 dark:text-white">
|
||||
All that’s left?
|
||||
</span>{" "}
|
||||
What makes your app your app.
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full mt-8 lg:mt-12">
|
||||
<div className="p-4 sm:p-8 rounded-xl bg-white shadow-sm dark:bg-stone-900">
|
||||
<DiagramAfterJazz className="w-full h-auto max-w-[30rem] mx-auto dark:text-white"></DiagramAfterJazz>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container flex flex-col gap-8 py-8 lg:gap-20 lg:py-20">
|
||||
<div className="flex flex-col gap-4 md:gap-6">
|
||||
<SectionHeader
|
||||
title="Everything you need to ship top-tier apps quickly."
|
||||
slogan="Features that used to take months to build now work out-of-the-box."
|
||||
/>
|
||||
|
||||
<GappedGrid>
|
||||
{[localFirst, ...features].map(
|
||||
({ title, icon: Icon, description }) => (
|
||||
<LabelledFeatureIcon
|
||||
className="col-span-2"
|
||||
key={title}
|
||||
label={title}
|
||||
icon={Icon}
|
||||
explanation={description}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</GappedGrid>
|
||||
<HairlineBleedGrid>
|
||||
{features.map(({ title, icon: Icon, description }) => (
|
||||
<LabelledFeatureIcon
|
||||
key={title}
|
||||
label={title}
|
||||
icon={Icon}
|
||||
explanation={description}
|
||||
/>
|
||||
))}
|
||||
</HairlineBleedGrid>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -251,7 +332,7 @@ export default function Home() {
|
||||
slogan="A chat app in 174 lines of code."
|
||||
/>
|
||||
|
||||
<div className="flex flex-col md:grid md:grid-cols-2 md:divide-x border rounded-sm overflow-hidden shadow-sm dark:divide-stone-900">
|
||||
<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={[
|
||||
{
|
||||
@@ -277,14 +358,14 @@ export default function Home() {
|
||||
]}
|
||||
/>
|
||||
<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:bg-stone-900">
|
||||
<div className="flex border-b overflow-x-auto overflow-y-hidden bg-white dark:border-stone-900 dark:bg-stone-900">
|
||||
<p className="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"
|
||||
localSrc="http://localhost:5173"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -307,6 +388,17 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SectionHeader
|
||||
title="Collaborative Values"
|
||||
slogan="Your new building blocks."
|
||||
/>
|
||||
|
||||
<Prose>
|
||||
<CoValuesIntro />
|
||||
</Prose>
|
||||
</div>
|
||||
|
||||
<GappedGrid
|
||||
title="Bread-and-butter datastructures"
|
||||
className="grid-cols-2 lg:grid-cols-4"
|
||||
@@ -315,18 +407,18 @@ export default function Home() {
|
||||
<H3>
|
||||
<CodeRef>CoMap</CodeRef>
|
||||
</H3>
|
||||
<Prose size="sm">
|
||||
<SmallProse>
|
||||
<CoMapDescription />
|
||||
</Prose>
|
||||
</SmallProse>
|
||||
</GridCard>
|
||||
|
||||
<GridCard>
|
||||
<H3>
|
||||
<CodeRef>CoList</CodeRef>
|
||||
</H3>
|
||||
<Prose size="sm">
|
||||
<SmallProse>
|
||||
<CoListDescription />
|
||||
</Prose>
|
||||
</SmallProse>
|
||||
</GridCard>
|
||||
|
||||
<GridCard>
|
||||
@@ -334,18 +426,18 @@ export default function Home() {
|
||||
<CodeRef>CoPlainText</CodeRef> &{" "}
|
||||
<CodeRef>CoRichText</CodeRef> <ComingSoonBadge />
|
||||
</H3>
|
||||
<Prose size="sm">
|
||||
<SmallProse>
|
||||
<CoPlainTextDescription />
|
||||
</Prose>
|
||||
</SmallProse>
|
||||
</GridCard>
|
||||
|
||||
<GridCard>
|
||||
<H3>
|
||||
<CodeRef>CoStream</CodeRef>
|
||||
</H3>
|
||||
<Prose size="sm">
|
||||
<SmallProse>
|
||||
<CoStreamDescription />
|
||||
</Prose>
|
||||
</SmallProse>
|
||||
</GridCard>
|
||||
</GappedGrid>
|
||||
|
||||
@@ -357,18 +449,18 @@ export default function Home() {
|
||||
<H3>
|
||||
<CodeRef>BinaryCoStream</CodeRef>
|
||||
</H3>
|
||||
<Prose size="sm">
|
||||
<SmallProse>
|
||||
<BinaryCoStreamDescription />
|
||||
</Prose>
|
||||
</SmallProse>
|
||||
</GridCard>
|
||||
|
||||
<GridCard>
|
||||
<H3>
|
||||
<CodeRef>ImageDefinition</CodeRef>
|
||||
</H3>
|
||||
<Prose size="sm">
|
||||
<SmallProse>
|
||||
<ImageDefinitionDescription />
|
||||
</Prose>
|
||||
</SmallProse>
|
||||
</GridCard>
|
||||
</GappedGrid>
|
||||
|
||||
@@ -380,82 +472,98 @@ export default function Home() {
|
||||
<H3>
|
||||
<CodeRef>Group</CodeRef>
|
||||
</H3>
|
||||
<Prose size="sm">
|
||||
<SmallProse>
|
||||
<GroupDescription />
|
||||
</Prose>
|
||||
</SmallProse>
|
||||
</GridCard>
|
||||
<GridCard>
|
||||
<H3>
|
||||
<CodeRef>Account</CodeRef>
|
||||
</H3>
|
||||
<Prose size="sm">
|
||||
<SmallProse>
|
||||
<AccountDescription />
|
||||
</Prose>
|
||||
</SmallProse>
|
||||
</GridCard>
|
||||
</GappedGrid>
|
||||
|
||||
<SupportedEnvironments />
|
||||
|
||||
<h2 className="sr-only">More features</h2>
|
||||
|
||||
<GappedGrid>
|
||||
<GridCard>
|
||||
<H3>Auto-sub</H3>
|
||||
<P className="text-lg">
|
||||
Let your UI drive data-syncing.
|
||||
</P>
|
||||
<Prose size="sm">
|
||||
<SectionHeader
|
||||
title="Auto-sub"
|
||||
slogan="Let your UI drive data-syncing."
|
||||
/>
|
||||
<SmallProse>
|
||||
<AutoSubDescription />
|
||||
</Prose>
|
||||
</SmallProse>
|
||||
</GridCard>
|
||||
|
||||
<GridCard>
|
||||
<H3>Cursors & carets</H3>
|
||||
<P className="text-lg">Ready-made spatial presence.</P>
|
||||
<Prose size="sm">
|
||||
<SectionHeader
|
||||
title="Cursors & carets"
|
||||
slogan="Ready-made spatial presence."
|
||||
/>
|
||||
<SmallProse>
|
||||
<CursorsAndCaretsDescription />
|
||||
</Prose>
|
||||
</SmallProse>
|
||||
</GridCard>
|
||||
|
||||
<GridCard>
|
||||
<H3>Two-way sync to your DB</H3>
|
||||
<P className="text-lg">Add Jazz to an existing app.</P>
|
||||
<Prose size="sm">
|
||||
<SectionHeader
|
||||
title="Auth Providers"
|
||||
slogan="Plug and play different kinds of auth."
|
||||
/>
|
||||
<SmallProse>
|
||||
<AuthProvidersDescription />
|
||||
</SmallProse>
|
||||
</GridCard>
|
||||
|
||||
<GridCard>
|
||||
<SectionHeader
|
||||
title="Two-way sync to your DB"
|
||||
slogan="Add Jazz to an existing app."
|
||||
/>
|
||||
<SmallProse>
|
||||
<TwoWaySyncDescription />
|
||||
</Prose>
|
||||
</SmallProse>
|
||||
</GridCard>
|
||||
|
||||
<GridCard>
|
||||
<H3>File upload & download</H3>
|
||||
<P className="text-lg">
|
||||
Just use <CodeRef>{`<input type='file'/>`}</CodeRef>
|
||||
.
|
||||
</P>
|
||||
<Prose size="sm">
|
||||
<SectionHeader
|
||||
title="File upload & download"
|
||||
slogan={
|
||||
<>
|
||||
Just use{" "}
|
||||
<CodeRef>{`<input type='file'/>`}</CodeRef>.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<SmallProse>
|
||||
<FileUploadDownloadDescription />
|
||||
</Prose>
|
||||
</SmallProse>
|
||||
</GridCard>
|
||||
|
||||
<GridCard>
|
||||
<H3>Video presence & calls</H3>
|
||||
<P className="text-lg">
|
||||
Stream and record audio & video.
|
||||
</P>
|
||||
<Prose size="sm">
|
||||
<SectionHeader
|
||||
title="Video presence & calls"
|
||||
slogan="Stream and record audio & video."
|
||||
/>
|
||||
<SmallProse>
|
||||
<VideoPresenceCallsDescription />
|
||||
</Prose>
|
||||
</SmallProse>
|
||||
</GridCard>
|
||||
</GappedGrid>
|
||||
|
||||
<div className="border rounded-xl shadow-sm p-4 md:py-16">
|
||||
<div className="lg:max-w-3xl md:text-center mx-auto space-y-6">
|
||||
<p className="uppercase text-blue tracking-widest text-sm font-medium dark:text-stone-400">
|
||||
<div className="border border-stone-200 dark:border-stone-900 rounded-xl shadow-sm p-4 md:py-16">
|
||||
<div className="flex flex-col lg:max-w-3xl md:text-center mx-auto justify-between gap-6">
|
||||
<p className="uppercase text-blue tracking-wide text-sm font-medium dark:text-stone-400">
|
||||
Become an early adopter
|
||||
</p>
|
||||
<H2>
|
||||
<h3 className="font-display md:text-center text-stone-950 dark:text-white text-2xl md:text-3xl font-semibold tracking-tight">
|
||||
We'll help you build your next app with Jazz
|
||||
</H2>
|
||||
<Prose className="md:text-balance mx-auto">
|
||||
</h3>
|
||||
<div className="space-y-2 md:text-balance leading-relaxed">
|
||||
<p>
|
||||
It's early days, but we work hard every day
|
||||
to make Jazz a great tool for our users.
|
||||
@@ -466,7 +574,7 @@ export default function Home() {
|
||||
We'll prioritize features that you need to
|
||||
succeed.
|
||||
</p>
|
||||
</Prose>
|
||||
</div>
|
||||
<div className="flex md:justify-center gap-3">
|
||||
<Button
|
||||
href="https://discord.gg/utDMjHYg42"
|
||||
|
||||
7
homepage/homepage/app/(home)/toolkit/authProviders.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { ComingSoonBadge } from "gcmp-design-system/src/app/components/atoms/ComingSoonBadge";
|
||||
|
||||
|
||||
- DemoAuth (for quick multi-user demos)
|
||||
- WebAuthN (TouchID/FaceID)
|
||||
- Auth0, Clerk & Okta <ComingSoonBadge/>
|
||||
- NextAuth <ComingSoonBadge/>
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
|
||||
export default function DocsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Prose className="container w-full max-w-full col-span-12 md:col-span-8 lg:col-span-9">
|
||||
{children}
|
||||
</Prose>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Prose>
|
||||
<h1>Welcome to the Jazz documentation.</h1>
|
||||
<p>
|
||||
The Jazz docs are currently heavily work in progress, sorry about that!
|
||||
</p>
|
||||
<p>The best ways to get started are:</p>
|
||||
<ul>
|
||||
<li>Quickstart (incomplete)<ol>
|
||||
<li><a href="/docs/sync-and-storage">Sync & Storage Setup</a></li>
|
||||
<li><a href="/docs/project-setup/react">React Project Setup</a></li>
|
||||
<li><a href="/docs/schemas/covalues">CoValue Basics & Schema Definition</a></li>
|
||||
<li><span className="opacity-50">Creating Covalues</span></li>
|
||||
<li><span className="opacity-50">Using Covalues</span></li>
|
||||
</ol></li>
|
||||
<li>The step-by-step <a href="/docs/guide">Guide</a> (incomplete)</li>
|
||||
</ul>
|
||||
<p>Also make sure to:</p>
|
||||
<ul>
|
||||
<li>Find an <a href="/docs/examples">example app with code</a> most similar to what you want to build</li>
|
||||
<li>Check out the <a href="/docs/api-reference">API Reference</a> (incomplete)</li>
|
||||
</ul>
|
||||
<p>And the best way to get help is to join the <a href="https://discord.gg/utDMjHYg42">Discord</a>!</p>
|
||||
</Prose>
|
||||
);
|
||||
}
|
||||
@@ -63,7 +63,7 @@ export default function Page() {
|
||||
href={`/docs/api-reference/${name}`}
|
||||
key={name}
|
||||
>
|
||||
<Card className="border shadow-sm">
|
||||
<Card className="border border-stone-200 dark:border-stone-900 shadow-sm">
|
||||
<PackageIcon
|
||||
size={25}
|
||||
strokeWidth={1.5}
|
||||
@@ -88,11 +88,11 @@ export default function Page() {
|
||||
</CardHeading>
|
||||
<CardBody>
|
||||
Get help from our{" "}
|
||||
<Link href="https://discord.gg/utDMjHYg42" className="underline">
|
||||
<Link href="https://discord.gg/utDMjHYg42">
|
||||
Discord
|
||||
</Link>
|
||||
, or open an issue on{" "}
|
||||
<Link href="https://github.com/gardencmp/jazz" className="underline">
|
||||
<Link href="https://github.com/gardencmp/jazz">
|
||||
GitHub
|
||||
</Link>
|
||||
.
|
||||
@@ -236,7 +236,7 @@ export default App; // old
|
||||
<div className="text-xs uppercase text-stone-400 dark:text-stone-600 tracking-wider -mb-3">
|
||||
Preview
|
||||
</div>
|
||||
<div className="p-3 md:-mx-3 rounded border border-stone-100 bg-white dark:bg-black not-prose">
|
||||
<div className="p-3 md:-mx-3 rounded border border-stone-100 dark:border-stone-900 bg-white dark:bg-black not-prose">
|
||||
<div className="grid grid-cols-6 text-sm border-r border-b [&>*]:p-2 [&>*]:border-l [&>*]:border-t">
|
||||
<h2>Buy terrarium</h2>
|
||||
<p className="col-span-3">Make sure it's big enough for 10 snails.</p>
|
||||
@@ -349,7 +349,7 @@ export function IssueComponent({ issue }: { issue: Issue }) { // old
|
||||
<div className="text-xs uppercase text-stone-400 dark:text-stone-600 tracking-wider -mb-3">
|
||||
Preview
|
||||
</div>
|
||||
<div className="p-3 md:-mx-3 rounded border border-stone-100 bg-white dark:bg-black not-prose">
|
||||
<div className="p-3 md:-mx-3 rounded border border-stone-100 dark:border-stone-900 bg-white dark:bg-black not-prose">
|
||||
<div className="grid grid-cols-6 text-sm border-r border-b [&>*]:p-2 [&>*]:border-l [&>*]:border-t">
|
||||
<input type="text" value={"Buy terrarium"} />
|
||||
<input
|
||||
@@ -1,89 +0,0 @@
|
||||
import Guide from "./guide.mdx";
|
||||
import { TableOfContents } from "@/components/docs/TableOfContents";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
name: "Project Setup",
|
||||
href: "/docs/guide#project-setup",
|
||||
},
|
||||
{
|
||||
name: "Intro to CoValues",
|
||||
href: "/docs/guide#intro-to-covalues",
|
||||
items: [
|
||||
{
|
||||
name: "Declaration",
|
||||
href: "/docs/guide#declaring-covalues",
|
||||
},
|
||||
{
|
||||
name: "Reading",
|
||||
href: "/docs/guide#reading-covalues",
|
||||
},
|
||||
{
|
||||
name: "Creation",
|
||||
href: "/docs/guide#creating-covalues",
|
||||
},
|
||||
{
|
||||
name: "Editing & Subscription",
|
||||
href: "/docs/guide#editing-and-subscription",
|
||||
},
|
||||
{
|
||||
name: "Persistence",
|
||||
href: "/docs/guide#persistence",
|
||||
},
|
||||
{
|
||||
name: "Remote Sync",
|
||||
href: "/docs/guide#remote-sync",
|
||||
},
|
||||
{
|
||||
name: "Simple Public Sharing",
|
||||
href: "/docs/guide#simple-public-sharing",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Refs & Auto-Subscribe",
|
||||
href: "/docs/guide#refs-and-on-demand-subscribe",
|
||||
items: [
|
||||
{
|
||||
name: "Precise Loading Depths",
|
||||
href: "/docs/guide#loading-depth",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Groups & Permissions",
|
||||
href: "/docs/guide#groups-and-permissions",
|
||||
items: [
|
||||
{
|
||||
name: "Groups/Accounts as Scopes",
|
||||
href: "/docs/guide#groups-accounts-as-scopes",
|
||||
},
|
||||
{
|
||||
name: "Creating Invites",
|
||||
href: "/docs/guide#creating-invites",
|
||||
},
|
||||
{
|
||||
name: "Consuming Invites",
|
||||
href: "/docs/guide#consuming-invites",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"col-span-12 md:col-span-8 lg:col-span-9",
|
||||
"lg:flex lg:gap-5",
|
||||
)}
|
||||
>
|
||||
<Prose className="overflow-x-hidden lg:flex-1">
|
||||
<Guide />
|
||||
</Prose>
|
||||
<TableOfContents className="w-48 shrink-0" items={navItems} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import { DocNav } from "@/components/docs/nav";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export const metadata = {
|
||||
title: "Docs",
|
||||
@@ -11,9 +13,17 @@ export default function DocsLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="container relative grid grid-cols-12 gap-5 py-8">
|
||||
<DocNav />
|
||||
{children}
|
||||
<div className="container relative grid grid-cols-12 gap-x-8 py-8">
|
||||
<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",
|
||||
"hidden md:block",
|
||||
)}
|
||||
/>
|
||||
<div className="col-span-12 md:col-span-8 lg:col-span-9">
|
||||
<Prose>{children}</Prose>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
5
homepage/homepage/app/docs/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import Guide from "./guide.mdx";
|
||||
|
||||
export default function Page() {
|
||||
return <Guide />;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import ReactGuide from "./react.mdx"
|
||||
import { TableOfContents } from "@/components/docs/TableOfContents";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
name: "React",
|
||||
href: "/docs/project-setup/react#react",
|
||||
},
|
||||
{
|
||||
name: "Next.JS",
|
||||
href: "/docs/project-setup/react#next",
|
||||
items: [
|
||||
{
|
||||
name: "Client-side only",
|
||||
href: "/docs/project-setup/react#next-csr",
|
||||
},
|
||||
{
|
||||
name: "SSR use 🧪",
|
||||
href: "/docs/project-setup/react#next-ssr",
|
||||
},
|
||||
{
|
||||
name: "SSR + client-side 🧪",
|
||||
href: "/docs/project-setup/react#next-ssr-plus-csr",
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "React Native",
|
||||
href: "/docs/project-setup/react#react-native"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"col-span-12 md:col-span-8 lg:col-span-9",
|
||||
"lg:flex lg:gap-5",
|
||||
)}
|
||||
>
|
||||
<Prose className="overflow-x-hidden lg:flex-1">
|
||||
<ReactGuide />
|
||||
</Prose>
|
||||
<TableOfContents className="w-48 shrink-0" items={navItems} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
import { CodeGroup } from "@/components/forMdx";
|
||||
|
||||
# React
|
||||
|
||||
Currently, the recommended pattern to set up a React app with Jazz is to create a separate file (for example, called `jazz.tsx`) in which:
|
||||
|
||||
1. You create a new Jazz React app context, extracting and exporting Jazz hooks.
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { createJazzReactApp } from "jazz-react";
|
||||
|
||||
const Jazz = createJazzReactApp();
|
||||
export const { useAccount, useCoState } = Jazz;
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
- You extract hooks here so they can be aware of a custom `AccountSchema` for your app once you start using one (see [Accounts & Migrations](/docs/schemas/accounts-and-migrations)).
|
||||
|
||||
This way, accounts returned by the hooks will be correctly typed throughout your app. Simply import them from `jazz.tsx` wherever you need them.
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { createJazzReactApp } from "jazz-react"; // old
|
||||
|
||||
const Jazz = createJazzReactApp({ AccountSchema: MyAppAccount });
|
||||
export const { useAccount, useCoState } = Jazz; // old
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
2. You define a context providing component (typically called `JazzAndAuth`) that uses
|
||||
|
||||
- the context provider of the Jazz React app context you just created
|
||||
|
||||
- the hooks and default/custom UI of one of the [Auth Methods](/docs/auth/auth-methods).
|
||||
|
||||
This is also where you specify the sync & storage server to connect to (see [Sync and storage](/docs/sync-and-storage)).
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { createJazzReactApp } from "jazz-react";// old
|
||||
|
||||
const Jazz = createJazzReactApp();// old
|
||||
export const { useAccount, useCoState } = Jazz;// old
|
||||
|
||||
import { PasskeyAuthBasicUI, usePasskeyAuth } from "jazz-react";
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const [passkeyAuth, passKeyState] = usePasskeyAuth({ appName });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Jazz.Provider
|
||||
auth={passkeyAuth}
|
||||
peer="wss://mesh.jazz.tools/?key=you@example.com"
|
||||
>
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
<PasskeyAuthBasicUI state={passKeyState} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
With `JazzAndAuth` defined, you can wrap your app in it and then use the extracted and exported hooks within your App.
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render( // old
|
||||
<React.StrictMode> // old
|
||||
<JazzAndAuth>
|
||||
<App />
|
||||
</JazzAndAuth>
|
||||
</React.StrictMode>// old
|
||||
);// old
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
# Next.JS
|
||||
|
||||
## Client-side only
|
||||
|
||||
The easiest way to use Jazz with Next.JS is to only use it on the client side. You can ensure this by:
|
||||
|
||||
- marking the `jazz.tsx` file as `"use client"`
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
"use client"
|
||||
import { createJazzReactApp } from "jazz-react";// old
|
||||
|
||||
const Jazz = createJazzReactApp();// old
|
||||
export const { useAccount, useCoState } = Jazz;// old
|
||||
|
||||
import { PasskeyAuthBasicUI, usePasskeyAuth } from "jazz-react";// old
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) {// old
|
||||
const [passkeyAuth, passKeyState] = usePasskeyAuth({ appName });// old
|
||||
|
||||
return (// old
|
||||
<>// old
|
||||
<Jazz.Provider// old
|
||||
auth={passkeyAuth}// old
|
||||
peer="wss://mesh.jazz.tools/?key=you@example.com"// old
|
||||
>// old
|
||||
{children}// old
|
||||
</Jazz.Provider>// old
|
||||
<PasskeyAuthBasicUI state={passKeyState} />// old
|
||||
</>// old
|
||||
);// old
|
||||
}// old
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
- marking any file with components where you use Jazz hooks (such as `useAccount` or `useCoState`) as `"use client"`
|
||||
|
||||
## SSR use (experimental)
|
||||
|
||||
Pure SSR use of Jazz is basically just using jazz-nodejs (see [Node.JS / Server Workers](/docs/project-setup/server-side)) inside Server Components.
|
||||
|
||||
Instead of using hooks as you would on the client, you await promises returned by `CoValue.load(...)` inside your Server Components.
|
||||
|
||||
TODO: code example
|
||||
|
||||
This should work well for cases like rendering publicly-readable information, since the worker account will be able to load them.
|
||||
|
||||
In the future, it will be possible to use trusted auth methods (such as Clerk, Auth0, etc.) that let you act as the same Jazz user both on the client and on the server, letting you use SSR even for data private to that user.
|
||||
|
||||
## SSR + client-side (experimental)
|
||||
|
||||
You can combine the two approaches by creating
|
||||
|
||||
1. A pure "rendering" component that renders an already-loaded CoValue (in JSON-ified form)
|
||||
|
||||
TODO: code example
|
||||
|
||||
2. A "hydrating" component (with `"use client"`) that
|
||||
|
||||
- expects a pre-loaded CoValue as a prop (in JSON-ified form)
|
||||
- uses one of the client-side Jazz hooks (such as `useAccount` or `useCoState`) to subscribe to that same CoValue
|
||||
- passing the client-side subscribed state to the "rendering" component, with the pre-loaded CoValue as a fallback until the client receives the first subscribed state
|
||||
|
||||
TODO: code example
|
||||
|
||||
3. A "pre-loading" Server Component that
|
||||
|
||||
- pre-loads the CoValue by awaiting it's `load(...)` method (as described above)
|
||||
- renders the "hydrating" component, passing the pre-loaded CoValue as a prop
|
||||
|
||||
TODO: code example
|
||||
|
||||
# React Native
|
||||
|
||||
TODO
|
||||
@@ -1,40 +0,0 @@
|
||||
import ServerGuide from "./server-side.mdx"
|
||||
import { TableOfContents } from "@/components/docs/TableOfContents";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
name: "Generating Credentials",
|
||||
href: "/docs/project-setup/server-side#generating-credentials",
|
||||
},
|
||||
{
|
||||
name: "Storing and Providing Credentials",
|
||||
href: "/docs/project-setup/server-side#storing-credentials",
|
||||
},
|
||||
{
|
||||
name: "Starting a Server Worker",
|
||||
href: "/docs/project-setup/server-side#starting",
|
||||
},
|
||||
{
|
||||
name: "Using CoValues instead of Requests",
|
||||
href: "/docs/project-setup/server-side#covalues-instead-of-requests",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"col-span-12 md:col-span-8 lg:col-span-9",
|
||||
"lg:flex lg:gap-5",
|
||||
)}
|
||||
>
|
||||
<Prose className="overflow-x-hidden lg:flex-1">
|
||||
<ServerGuide />
|
||||
</Prose>
|
||||
<TableOfContents className="w-48 shrink-0" items={navItems} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import { CodeGroup } from "@/components/forMdx";
|
||||
|
||||
# Node.JS / Server Workers
|
||||
|
||||
The main detail to understand when using Jazz server-side is that Server Workers have Jazz `Accounts`, just like normal users do.
|
||||
|
||||
This lets you share CoValues with Server Workers, having precise access control by adding the Worker to `Groups` with specific roles just like you would with other users.
|
||||
|
||||
## Generating Credentials
|
||||
|
||||
Server Workers typically have static credentials, consisting of a public Account ID and a private Account Secret.
|
||||
|
||||
To generate new credentials for a Server Worker, you can run:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```sh
|
||||
npx jazz-run account create --name "My Server Worker"
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
The name will be put in the public profile of the Server Worker's `Account`, which can be helpful when inspecting metadata of CoValue edits that the Server Worker has done.
|
||||
|
||||
## Storing & Providing Credentials
|
||||
|
||||
Server Worker credentials are typically stored and provided as environmental variables.
|
||||
|
||||
**Take extra care with the Account Secret — handle it like any other secret environment variable such as a DB password.**
|
||||
|
||||
## Starting a Server Worker
|
||||
|
||||
You can use `startWorker` from `jazz-nodejs` to start a Server Worker. Similarly to setting up a client-side Jazz context, it:
|
||||
|
||||
- takes a custom `AccountSchema` if you have one (for example, because the worker needs to store information in it's private account root)
|
||||
- takes a URL for a sync & storage server
|
||||
|
||||
`startWorker` expects credentials in the `JAZZ_WORKER_ACCOUNT` and `JAZZ_WORKER_SECRET` environment variables by default (as printed by `npx account create ...`), but you can also pass them manually as `accountID` and `accountSecret` parameters if you get them from elsewhere.
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```ts
|
||||
import { startWorker } from 'jazz-nodejs';
|
||||
|
||||
const { worker } = await startWorker({
|
||||
AccountSchema: MyWorkerAccount,
|
||||
syncServer: 'wss://cloud.jazz.tools/?key=you@example.com',
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
`worker` acts like `me` (as returned by `useAccount` on the client) - you can use it to:
|
||||
|
||||
- load/subscribe to CoValues: `MyCoValue.subscribe(id, worker, {...})`
|
||||
- create CoValues & Groups `const val = MyCoValue.create({...}, { owner: worker })`
|
||||
|
||||
## Using CoValues instead of Requests
|
||||
|
||||
Just like traditional backend functions, you can use Server Workers to do useful stuff (computations, calls to third-party APIs etc.) and put the results back into CoValues, which subscribed clients automatically get notified about.
|
||||
|
||||
What's less clear is how you can trigger this work to happen.
|
||||
|
||||
- One option is to define traditional HTTP API handlers that use the Jazz Worker internally. This is helpful if you need to mutate Jazz state in response to HTTP requests such as for webhooks or non-Jazz API clients
|
||||
- The other option is to have the Jazz Worker subscribe to CoValues which they will then collaborate on with clients.
|
||||
- A common pattern is to implement a state machine represented by a CoValue, where the client will do some state transitions (such as `draft -> ready`), which the worker will notice and then do some work in response, feeding the result back in a further state transition (such as `ready -> success & data`, or `ready -> failure & error details`).
|
||||
- This way, client and worker don't have to explicitly know about each other or communicate directly, but can rely on Jazz as a communication mechanism - with computation progressing in a distributed manner wherever and whenever possible.
|
||||
@@ -1,65 +0,0 @@
|
||||
import { CodeGroup } from "@/components/forMdx";
|
||||
|
||||
# CoValues
|
||||
|
||||
**CoValues ("Collaborative Values") are the core abstraction of Jazz.** Think of them as bread-and-butter datastructures that you can use to represent everything you need in your app.
|
||||
|
||||
As their name suggests, they are inherently collaborative, meaning **multiple users and devices can edit them at the same time.**
|
||||
|
||||
- CoValues allow for concurrent edits by always keeping their full edit histories, deriving the "current state" based on the currently locally available history.
|
||||
- **Think of CoValues as "super-fast Git for lots of tiny data".**
|
||||
- (The fact that this happens in an eventually-consistent way makes them [CRDTs](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type))
|
||||
- Having the full history around also means that you often don't need explicit timestamps and author info - you get this for free, just by having different accounts edit a value and then looking at its [edit metadata](/docs/covalues/metadata).
|
||||
|
||||
CoValues mostly model JSON with CoMaps and CoLists, but also offer CoStreams for simple per-user value streams, and also let you represent binary data with BinaryCoStreams.
|
||||
|
||||
## Schemas as Your App's First Step
|
||||
|
||||
Under the hood, CoValues are as dynamic and flexible as JSON itself, but the way you use them in Jazz is by defining fixed schemas to describe the shape of data in your app.
|
||||
|
||||
- This helps correctness and development speed in general, but is particularly important
|
||||
- when you evolve your app and need migrations
|
||||
- when different clients and server workers collaborate on CoValues and need to make compatible changes
|
||||
|
||||
- Luckily, thinking about the shape of your data first is also a really good way to model your app. Even before you know the details of how your app will work, you'll probably know which kinds of objects it will deal with, and how they relate to each other.
|
||||
|
||||
Jazz makes it really quick to define and update schemas, since they are simple TypeScript classes:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```ts
|
||||
export class TodoProject extends CoMap {
|
||||
title = co.string;
|
||||
tasks = co.ref(ListOfTasks);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Here you can see how we use the `co` definer for declaring collaboratively editable fields, which ensures that schema info is correct at the type level and at runtime.
|
||||
|
||||
Classes might look a bit old-fashioned to some, but one nice property they have is that they are both types and values in TypeScript, so we can use them as both (with a single definition & import).
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```ts
|
||||
import { TodoProject } from "./schema";
|
||||
|
||||
const project: TodoProject = TodoProject.create(
|
||||
{
|
||||
title: "New Project",
|
||||
tasks: ListOfTasks.create([], { owner: me }),
|
||||
},
|
||||
{ owner: me }
|
||||
);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## CoValue field types
|
||||
|
||||
Before we look at the different types of CoValues, let's learn what we can put *into* them:
|
||||
|
||||
### Primitive fields
|
||||
|
||||
### Refs
|
||||
|
||||
### Computed fields, methods & constructors
|
||||
@@ -1,76 +0,0 @@
|
||||
import CoValuesGuide from "./covalues.mdx"
|
||||
import { TableOfContents } from "@/components/docs/TableOfContents";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
name: "CoValues",
|
||||
href: "/docs/schemas/covalues",
|
||||
},
|
||||
{
|
||||
name: "Schemas as Your App's First Step",
|
||||
href: "/docs/schemas/covalues#schemas-as-first-step",
|
||||
},
|
||||
{
|
||||
name: "CoValue field types",
|
||||
href: "/docs/schemas/covalues#field-types",
|
||||
items: [
|
||||
{
|
||||
name: "Primitive Fields",
|
||||
href: "/docs/schemas/covalues#primitive-fields",
|
||||
},
|
||||
{
|
||||
name: "Refs",
|
||||
href: "/docs/schemas/covalues#refs",
|
||||
},
|
||||
{
|
||||
name: "Computed Fields, Methods & Constructors ",
|
||||
href: "/docs/schemas/covalues#custom-fields"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "CoMaps",
|
||||
href: "/docs/schemas/covalues#comaps",
|
||||
items: [
|
||||
{
|
||||
name: "Struct-like CoMaps",
|
||||
href: "/docs/schemas/covalues#comaps-struct-like",
|
||||
},
|
||||
{
|
||||
name: "Dict/Record-like CoMaps",
|
||||
href: "/docs/schemas/covalues#comaps-dict-like",
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "CoLists",
|
||||
href: "/docs/schemas/covalues#colists",
|
||||
},
|
||||
{
|
||||
name: "CoStreams",
|
||||
href: "/docs/schemas/covalues#costreams",
|
||||
},
|
||||
{
|
||||
name: "BinaryCoStreams",
|
||||
href: "/docs/schemas/covalues#binarycostreams",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"col-span-12 md:col-span-8 lg:col-span-9",
|
||||
"lg:flex lg:gap-5",
|
||||
)}
|
||||
>
|
||||
<Prose className="overflow-x-hidden lg:flex-1">
|
||||
<CoValuesGuide />
|
||||
</Prose>
|
||||
<TableOfContents className="w-48 shrink-0" items={navItems} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import SyncAndStorage from "./sync-and-storage.mdx"
|
||||
import { TableOfContents } from "@/components/docs/TableOfContents";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
name: "Using Jazz Cloud",
|
||||
href: "/docs/sync-and-storage#using-jazz-cloud",
|
||||
items: [
|
||||
{
|
||||
name: "Free Public Alpha",
|
||||
href: "/docs/sync-and-storage#free-public-alpha",
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Running your own sync server",
|
||||
href: "/docs/sync-and-storage#running-your-own",
|
||||
items: [
|
||||
{
|
||||
name: "Command line options",
|
||||
href: "/docs/sync-and-storage#command-line-options",
|
||||
},
|
||||
{
|
||||
name: "Source code",
|
||||
href: "/docs/sync-and-storage#source-code",
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"col-span-12 md:col-span-8 lg:col-span-9",
|
||||
"lg:flex lg:gap-5",
|
||||
)}
|
||||
>
|
||||
<Prose className="overflow-x-hidden lg:flex-1">
|
||||
<SyncAndStorage />
|
||||
</Prose>
|
||||
<TableOfContents className="w-48 shrink-0" items={navItems} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
# Sync and Storage
|
||||
|
||||
## Using Jazz Cloud
|
||||
|
||||
Simply use `wss://cloud.jazz.tools/?key=...` as the sync server URL.
|
||||
|
||||
Jazz Cloud will
|
||||
- sync CoValues in real-time between users and devices
|
||||
- safely persist CoValues on redundant storage nodes with additional backups
|
||||
- make use of geographically distributed cache nodes for low latency
|
||||
|
||||
### Free Public Alpha <a id="free-public-alpha"/>
|
||||
|
||||
- Jazz Cloud is free during the public alpha, with no strict usage limits
|
||||
- We plan to keep a free tier, so you'll always be able to get started with zero setup
|
||||
- See [Jazz Cloud Pricing](https://jazz.tools/cloud#pricing) for more details
|
||||
- ⚠️ Please use a valid email address as your API key.
|
||||
|
||||
Your full sync server URL should look something like
|
||||
|
||||
```wss://cloud.jazz.tools/?key=you@example.com```
|
||||
|
||||
Once we support per-app API keys, we'll email you an API key you can use instead.
|
||||
|
||||
|
||||
## Running your own sync server <a id="running-your-own"/>
|
||||
|
||||
You can run your own sync server using:
|
||||
|
||||
```sh
|
||||
npx jazz-run sync
|
||||
```
|
||||
|
||||
And then use `ws://localhost:4200` as the sync server URL.
|
||||
|
||||
You can also run this simple sync server behind a proxy that supports WebSockets, for example to provide TLS.
|
||||
In this case, provide the WebSocket endpoint your proxy exposes as the sync server URL.
|
||||
|
||||
### Command line options: <a id="command-line-options"/>
|
||||
|
||||
- `--port` / `-p` - the port to run the sync server on. Defaults to 4200.
|
||||
- `--in-memory` - keep CoValues in-memory only and do sync only, no persistence. Persistence is enabled by default.
|
||||
- `--db` - the path to the file where to store the data (SQLite). Defaults to `sync-db/storage.db`.
|
||||
|
||||
### Source code <a id="source-code"/>
|
||||
|
||||
The implementation of this simple sync server is available open-source [on GitHub](https://github.com/gardencmp/jazz/blob/main/packages/jazz-run/src/startSync.ts).
|
||||
@@ -2,6 +2,78 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 20 14.3% 4.1%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 20 14.3% 4.1%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 20 14.3% 4.1%;
|
||||
|
||||
--primary: 24 9.8% 10%;
|
||||
--primary-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--secondary: 60 4.8% 95.9%;
|
||||
--secondary-foreground: 24 9.8% 10%;
|
||||
|
||||
--muted: 60 4.8% 95.9%;
|
||||
--muted-foreground: 25 5.3% 44.7%;
|
||||
|
||||
--accent: 60 4.8% 95.9%;
|
||||
--accent-foreground: 24 9.8% 10%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--border: 20 5.9% 90%;
|
||||
--input: 20 5.9% 90%;
|
||||
--ring: 20 14.3% 4.1%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 20 14.3% 4.1%;
|
||||
--foreground: 60 9.1% 97.8%;
|
||||
|
||||
--card: 20 14.3% 4.1%;
|
||||
--card-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--popover: 20 14.3% 4.1%;
|
||||
--popover-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--primary: 60 9.1% 97.8%;
|
||||
--primary-foreground: 24 9.8% 10%;
|
||||
|
||||
--secondary: 12 6.5% 15.1%;
|
||||
--secondary-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--muted: 12 6.5% 15.1%;
|
||||
--muted-foreground: 24 5.4% 63.9%;
|
||||
|
||||
--accent: 12 6.5% 15.1%;
|
||||
--accent-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--border: 12 6.5% 15.1%;
|
||||
--input: 12 6.5% 15.1%;
|
||||
--ring: 24 5.7% 82.9%;
|
||||
}
|
||||
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
.overlay-close {
|
||||
@apply bg-black;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
@import "shiki.css";
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import "./globals.css";
|
||||
import type { Metadata } from "next";
|
||||
import { ThemeProvider } from "@/components/themeProvider";
|
||||
|
||||
import { Inter, Manrope } from "next/font/google";
|
||||
import { Inter, Manrope, Sriracha } from "next/font/google";
|
||||
import localFont from "next/font/local";
|
||||
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
import { JazzNav } from "@/components/nav";
|
||||
import { JazzFooter } from "@/components/footer";
|
||||
import { ThemeProvider } from "gcmp-design-system/src/app/components/molecules/ThemeProvider";
|
||||
|
||||
// If loading a variable font, you don't need to specify the font weight
|
||||
const manrope = Manrope({
|
||||
@@ -23,6 +23,13 @@ const inter = Inter({
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const sriracha = Sriracha({
|
||||
weight: "400",
|
||||
subsets: ["latin"],
|
||||
variable: "--font-sriracha",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const commitMono = localFont({
|
||||
src: [
|
||||
{
|
||||
@@ -41,7 +48,7 @@ const commitMono = localFont({
|
||||
});
|
||||
|
||||
const metaTags = {
|
||||
title: "Jazz - Build local-first apps",
|
||||
title: "jazz - Build your next app with sync",
|
||||
description:
|
||||
"Jazz is an open-source framework for building local-first apps, removing 90% of the backend and infrastructure complexity.",
|
||||
url: "https://jazz.tools",
|
||||
@@ -51,16 +58,16 @@ export const metadata: Metadata = {
|
||||
// metadataBase is a convenience option to set a base URL prefix for metadata fields that require a fully qualified URL.
|
||||
metadataBase: new URL(metaTags.url),
|
||||
title: {
|
||||
template: "%s | Jazz",
|
||||
template: "%s | jazz",
|
||||
default: metaTags.title,
|
||||
},
|
||||
applicationName: "Jazz",
|
||||
applicationName: "jazz",
|
||||
description: metaTags.description,
|
||||
openGraph: {
|
||||
title: metaTags.title,
|
||||
description: metaTags.description,
|
||||
url: metaTags.url,
|
||||
siteName: "Jazz",
|
||||
siteName: "jazz",
|
||||
images: [
|
||||
{
|
||||
url: "/social-image.png",
|
||||
@@ -77,13 +84,14 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className="h-full" suppressHydrationWarning>
|
||||
<html lang="en">
|
||||
<body
|
||||
className={[
|
||||
manrope.variable,
|
||||
commitMono.variable,
|
||||
inter.className,
|
||||
"h-full flex flex-col items-center [&_*]:scroll-mt-[5rem]",
|
||||
sriracha.variable,
|
||||
"flex flex-col items-center [&_*]:scroll-mt-[5rem]",
|
||||
"bg-white text-stone-700 dark:text-stone-400 dark:bg-stone-950",
|
||||
].join(" ")}
|
||||
>
|
||||
|
||||
@@ -53,7 +53,7 @@ pre.twoslash data-lsp::before {
|
||||
position: absolute;
|
||||
transform: translate(0, 1.2rem);
|
||||
max-width: 30rem;
|
||||
@apply text-xs px-1.5 py-1 rounded border shadow-lg overflow-hidden whitespace-pre-wrap text-stone-700 bg-stone-50 dark:text-stone-200 dark:bg-stone-950;
|
||||
@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;
|
||||
@@ -79,7 +79,7 @@ pre.twoslash data-lsp:hover::before {
|
||||
}
|
||||
|
||||
pre .code-container {
|
||||
@apply overflow-auto p-2 pl-0 bg-white dark:bg-stone-925 rounded-b-xl text-xs h-full;
|
||||
@apply overflow-auto p-2 pl-0 bg-white dark:bg-stone-925 rounded-b-xl text-xs h-full dark:border-stone-900;
|
||||
}
|
||||
/* The try button */
|
||||
pre .code-container > a {
|
||||
|
||||