Compare commits
196 Commits
cojson@0.1
...
cojson@0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40e1ca7cb1 | ||
|
|
80cf21e453 | ||
|
|
48c8a3d219 | ||
|
|
31bb1201fc | ||
|
|
08d1b05607 | ||
|
|
d64a14210d | ||
|
|
7e53d33e9b | ||
|
|
ea2b39cc30 | ||
|
|
6b835f95cf | ||
|
|
a229ae5f70 | ||
|
|
84fdc1d8fd | ||
|
|
14a8b32522 | ||
|
|
ddc09a0d6b | ||
|
|
3b45a3f2fd | ||
|
|
9034a45da0 | ||
|
|
a5ceaffb0c | ||
|
|
dcee2f9b4e | ||
|
|
83fdc504ff | ||
|
|
a34c0675cd | ||
|
|
5a8a62b4a3 | ||
|
|
325a554bd1 | ||
|
|
7422943e83 | ||
|
|
23bfea5861 | ||
|
|
605a54eb11 | ||
|
|
a7aaee51e6 | ||
|
|
4b8983858a | ||
|
|
8a8c4d11e1 | ||
|
|
b774bb345d | ||
|
|
7fd891d7b9 | ||
|
|
27cac4a6d7 | ||
|
|
2b71ef1181 | ||
|
|
ae169c7b3a | ||
|
|
d888c99d9a | ||
|
|
0b54917f19 | ||
|
|
c87b215b75 | ||
|
|
e4ba23cbef | ||
|
|
98c005a6e0 | ||
|
|
8528db4de4 | ||
|
|
e0fe5a20b7 | ||
|
|
e16e4d53d1 | ||
|
|
d904fae506 | ||
|
|
f67c0b3db3 | ||
|
|
283d7c6bf0 | ||
|
|
0e7a7dbbc0 | ||
|
|
63c69b6b95 | ||
|
|
fd02627069 | ||
|
|
827adc991d | ||
|
|
651b69e5af | ||
|
|
277e4d49e8 | ||
|
|
cfa44f32eb | ||
|
|
ef920435e9 | ||
|
|
87d05404dd | ||
|
|
535c460f5a | ||
|
|
fa1b302474 | ||
|
|
45f73a774c | ||
|
|
2a9e271dc3 | ||
|
|
3d96d9c829 | ||
|
|
844051405d | ||
|
|
625eff2333 | ||
|
|
59e2871065 | ||
|
|
acdc88fb91 | ||
|
|
05eab4e2a9 | ||
|
|
efcd65ae38 | ||
|
|
ad60fa942a | ||
|
|
5272d3cd2a | ||
|
|
d837811813 | ||
|
|
2b4aba2d1b | ||
|
|
50b4da18d9 | ||
|
|
df22f2617e | ||
|
|
280495c533 | ||
|
|
d5c6fbdc3c | ||
|
|
57776a1400 | ||
|
|
156c45aa0e | ||
|
|
2d0dba6bbc | ||
|
|
7241d2ad95 | ||
|
|
4a9eeace00 | ||
|
|
4f9c91f6ff | ||
|
|
7ff13a8f55 | ||
|
|
5741d7f09c | ||
|
|
766d2c8846 | ||
|
|
70a43d0c39 | ||
|
|
cf44258848 | ||
|
|
4db5ec2dd8 | ||
|
|
4983e57e62 | ||
|
|
5b2fc70ca1 | ||
|
|
53af54570c | ||
|
|
e0b4626c22 | ||
|
|
a273a0db58 | ||
|
|
277104ebba | ||
|
|
434e59d5c4 | ||
|
|
bb20907774 | ||
|
|
282425575f | ||
|
|
75501a9051 | ||
|
|
c114cf4029 | ||
|
|
03da7dc994 | ||
|
|
12d5c68f98 | ||
|
|
ec1e359621 | ||
|
|
44fb13ddd7 | ||
|
|
4fd4c5bbed | ||
|
|
a1d31566ed | ||
|
|
dcde3aa811 | ||
|
|
a621141d76 | ||
|
|
0e1fbd7dfc | ||
|
|
27efdf9b8e | ||
|
|
83068b33e9 | ||
|
|
769d9b0517 | ||
|
|
b0ffe5ed7e | ||
|
|
c67a0cd3ce | ||
|
|
48e11a243f | ||
|
|
de904698d8 | ||
|
|
7c8180dcb4 | ||
|
|
da3e101c50 | ||
|
|
7605d228f2 | ||
|
|
3063d74ab9 | ||
|
|
2a4e3fc0cd | ||
|
|
7cc6d63d40 | ||
|
|
d574bbc521 | ||
|
|
6388a7272b | ||
|
|
cd358888d8 | ||
|
|
f427f2324b | ||
|
|
d0e2041b10 | ||
|
|
edce59d238 | ||
|
|
7f75d852c1 | ||
|
|
2683af7d28 | ||
|
|
4223720010 | ||
|
|
1b4508fea7 | ||
|
|
776fa09279 | ||
|
|
5c200aa60d | ||
|
|
9fc6c5f6c8 | ||
|
|
7e06cd4a77 | ||
|
|
c43191d97b | ||
|
|
5706b5eb81 | ||
|
|
5eed930997 | ||
|
|
35e5e50508 | ||
|
|
e88a3d0712 | ||
|
|
13b64af5b2 | ||
|
|
9804c6a729 | ||
|
|
937d415cc2 | ||
|
|
9196154207 | ||
|
|
c907b2aac2 | ||
|
|
b5a9dfa7ec | ||
|
|
113c77b416 | ||
|
|
cad5444400 | ||
|
|
b2b350e4d0 | ||
|
|
80d5d62852 | ||
|
|
75f8833c1a | ||
|
|
99d1f3f28b | ||
|
|
4066cfe011 | ||
|
|
e5f8b06af1 | ||
|
|
485b5a238d | ||
|
|
c42ee6d6e2 | ||
|
|
1fb2fd0f50 | ||
|
|
0fce4adfc5 | ||
|
|
b59086c808 | ||
|
|
fe91463652 | ||
|
|
b9065db109 | ||
|
|
d13da295ed | ||
|
|
0291389c3b | ||
|
|
f417f518cb | ||
|
|
f69f99a209 | ||
|
|
31c4fd7c07 | ||
|
|
613aadd775 | ||
|
|
0f969b4be4 | ||
|
|
f7ac1015b2 | ||
|
|
d7c5d4a03d | ||
|
|
8384c55cce | ||
|
|
be947d1086 | ||
|
|
3924bb2ede | ||
|
|
2e3003c2fc | ||
|
|
5ec15ba0a2 | ||
|
|
c7ccf5c5d7 | ||
|
|
9e8c81a1a6 | ||
|
|
c563e2547f | ||
|
|
00cb697bc7 | ||
|
|
a6a0560059 | ||
|
|
55fa5977c2 | ||
|
|
8b4bc1bc97 | ||
|
|
df403ccbc6 | ||
|
|
1f490d2aae | ||
|
|
1b766b6369 | ||
|
|
3f54da98f8 | ||
|
|
05b98c0f8b | ||
|
|
928e63620b | ||
|
|
ea9b1eb88e | ||
|
|
83a88252e2 | ||
|
|
7a61c19135 | ||
|
|
a2f87f304c | ||
|
|
f9f24d2ad2 | ||
|
|
ed3197a7fd | ||
|
|
772a88e98f | ||
|
|
e22a7f46ad | ||
|
|
f890f2f460 | ||
|
|
e9c17e12dc | ||
|
|
4b908e3024 | ||
|
|
90fd2b5da0 | ||
|
|
d7b4360f11 |
23
.github/pull_request_template.md
vendored
Normal file
23
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Description
|
||||
<!-- Please include a summary of the change and which issue is fixed -->
|
||||
<!-- Please also include relevant motivation and context -->
|
||||
<!-- Include any links to documentation like RFC’s if necessary -->
|
||||
<!-- Add a link to to relevant preview environments or anything that would simplify visual review process -->
|
||||
<!-- Supplemental screenshots and video are encouraged, but the primary description should be in text -->
|
||||
|
||||
## Manual testing instructions
|
||||
|
||||
<!-- Add any actions required to manually test the changes -->
|
||||
|
||||
## Tests
|
||||
|
||||
- [ ] Tests have been added and/or updated
|
||||
- [ ] Tests have not been updated, because: <!-- Insert reason for not updating tests here -->
|
||||
- [ ] I need help with writing tests
|
||||
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I've updated the part of the docs that are affected the PR changes
|
||||
- [ ] I've generated a changeset, if a version bump is required
|
||||
- [ ] I've updated the jsDoc comments to the public APIs I've modified, or added them when missing
|
||||
6
.github/workflows/code-quality.yml
vendored
6
.github/workflows/code-quality.yml
vendored
@@ -1,5 +1,11 @@
|
||||
name: Code quality
|
||||
|
||||
concurrency:
|
||||
# For pushes, this lets concurrent runs happen, so each push gets a result.
|
||||
# But for other events (e.g. PRs), we can cancel the previous runs.
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
|
||||
8
.github/workflows/e2e-rn-test.yml
vendored
8
.github/workflows/e2e-rn-test.yml
vendored
@@ -1,5 +1,11 @@
|
||||
name: End-to-End Tests for React Native
|
||||
|
||||
concurrency:
|
||||
# For pushes, this lets concurrent runs happen, so each push gets a result.
|
||||
# But for other events (e.g. PRs), we can cancel the previous runs.
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
@@ -61,7 +67,7 @@ jobs:
|
||||
disable-animations: true
|
||||
working-directory: ./examples/chat-rn-expo/
|
||||
# killall due to this issue: https://github.com/ReactiveCircus/android-emulator-runner/issues/385
|
||||
script: ./test/e2e/run.sh && killall -INT crashpad_handler || true
|
||||
script: ./test/e2e/run.sh && ( killall -INT crashpad_handler || true )
|
||||
|
||||
- name: Copy Maestro Output
|
||||
if: steps.e2e_test.outcome != 'success'
|
||||
|
||||
6
.github/workflows/jazz-run.yml
vendored
6
.github/workflows/jazz-run.yml
vendored
@@ -1,5 +1,11 @@
|
||||
name: Jazz Run Tests
|
||||
|
||||
concurrency:
|
||||
# For pushes, this lets concurrent runs happen, so each push gets a result.
|
||||
# But for other events (e.g. PRs), we can cancel the previous runs.
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
8
.github/workflows/playwright-homepage.yml
vendored
8
.github/workflows/playwright-homepage.yml
vendored
@@ -1,4 +1,10 @@
|
||||
name: Playwright Tests
|
||||
name: Playwright Tests - Homepage
|
||||
|
||||
concurrency:
|
||||
# For pushes, this lets concurrent runs happen, so each push gets a result.
|
||||
# But for other events (e.g. PRs), we can cancel the previous runs.
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
6
.github/workflows/playwright.yml
vendored
6
.github/workflows/playwright.yml
vendored
@@ -1,5 +1,11 @@
|
||||
name: Playwright Tests
|
||||
|
||||
concurrency:
|
||||
# For pushes, this lets concurrent runs happen, so each push gets a result.
|
||||
# But for other events (e.g. PRs), we can cancel the previous runs.
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
7
.github/workflows/pre-release.yml
vendored
7
.github/workflows/pre-release.yml
vendored
@@ -1,4 +1,11 @@
|
||||
name: Pre-Publish tagged Pull Requests
|
||||
|
||||
concurrency:
|
||||
# For pushes, this lets concurrent runs happen, so each push gets a result.
|
||||
# But for other events (e.g. PRs), we can cancel the previous runs.
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
|
||||
6
.github/workflows/unit-test.yml
vendored
6
.github/workflows/unit-test.yml
vendored
@@ -1,5 +1,11 @@
|
||||
name: Unit Tests
|
||||
|
||||
concurrency:
|
||||
# For pushes, this lets concurrent runs happen, so each push gets a result.
|
||||
# But for other events (e.g. PRs), we can cancel the previous runs.
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
@@ -63,7 +63,7 @@ You'll need Node.js 22.x installed (we're working on support for 23.x), and pnpm
|
||||
4. **Build the packages**:
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
pnpm build:packages
|
||||
```
|
||||
|
||||
5. **Run tests** to verify everything is working:
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# passkey-svelte
|
||||
|
||||
## 0.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a5ceaff]
|
||||
- jazz-tools@0.15.6
|
||||
|
||||
## 0.0.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [23bfea5]
|
||||
- Updated dependencies [e4ba23c]
|
||||
- Updated dependencies [4b89838]
|
||||
- jazz-tools@0.15.5
|
||||
|
||||
## 0.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-tools@0.15.4
|
||||
|
||||
## 0.0.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [45f73a7]
|
||||
- jazz-tools@0.15.3
|
||||
|
||||
## 0.0.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-svelte",
|
||||
"version": "0.0.91",
|
||||
"version": "0.0.95",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -58,7 +58,7 @@ export const TodoAccount = co
|
||||
/** The account migration is run on account creation and on every log-in.
|
||||
* You can use it to set up the account root and any other initial CoValues you need.
|
||||
*/
|
||||
if (!account.root) {
|
||||
if (account.root === undefined) {
|
||||
account.root = TodoAccountRoot.create({
|
||||
projects: co.list(TodoProject).create([], { owner: account }),
|
||||
});
|
||||
|
||||
@@ -17,7 +17,6 @@ import React from "react";
|
||||
import { TodoAccount, TodoProject } from "./1_schema.ts";
|
||||
import { NewProjectForm } from "./3_NewProjectForm.tsx";
|
||||
import { ProjectTodoTable } from "./4_ProjectTodoTable.tsx";
|
||||
import { apiKey } from "./apiKey";
|
||||
import {
|
||||
Button,
|
||||
ThemeProvider,
|
||||
@@ -42,7 +41,7 @@ function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<JazzReactProvider
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
peer: `ws://localhost:4200`,
|
||||
}}
|
||||
AccountSchema={TodoAccount}
|
||||
>
|
||||
@@ -53,6 +52,8 @@ function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
// http://localhost:5173/#/project/co_znUD6vciCQazKwAKi4hFwRodxEk
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<JazzAndAuth>
|
||||
|
||||
@@ -11,7 +11,9 @@ import { useNavigate } from "react-router";
|
||||
export function NewProjectForm() {
|
||||
// `me` represents the current user account, which will determine
|
||||
// access rights to CoValues. We get it from the top-level provider `<WithJazz/>`.
|
||||
const { me } = useAccount(TodoAccount);
|
||||
const { me } = useAccount(TodoAccount, {
|
||||
resolve: { root: { projects: { $each: { $onError: null } } } },
|
||||
});
|
||||
const navigate = useNavigate();
|
||||
|
||||
const createProject = useCallback(
|
||||
|
||||
@@ -5,6 +5,7 @@ export const COLORS = {
|
||||
GREEN: "#8BDA27",
|
||||
PINK: "#EF478E",
|
||||
PURPLE: "#B441EB",
|
||||
YELLOW: "#FBC400",
|
||||
YELLOW: "#FCAE00",
|
||||
RED: "#FF601C",
|
||||
ORANGE: "#FF601C",
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"radix-ui": "^1.4.2",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"resend": "^4.0.0",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
||||
|
Before Width: | Height: | Size: 629 B |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,47 +1,46 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--color-primary: #146aff;
|
||||
--color-primary-dark: lch(
|
||||
from var(--color-primary) calc(l - 15) calc(c - 1) calc(h + 10)
|
||||
from var(--color-primary) calc(l - 10) calc(c - 1) calc(h + 5)
|
||||
);
|
||||
--color-primary-light: lch(
|
||||
from var(--color-primary) calc(l + 15) calc(c + 1) calc(h - 10)
|
||||
from var(--color-primary) calc(l + 10) calc(c + 1) calc(h - 5)
|
||||
);
|
||||
|
||||
--color-secondary: var(--color-primary-dark);
|
||||
--color-highlight: #2dc9c9;
|
||||
--color-success: #42bb69;
|
||||
--color-info: #fbc400;
|
||||
--color-info: #b441eb;
|
||||
--color-warning: #ff601c;
|
||||
--color-tip: #b441eb;
|
||||
--color-success: #8bda27;
|
||||
--color-alert: #fcae00;
|
||||
--color-tip: #2dc9c9;
|
||||
--color-pink: #ef478e;
|
||||
--color-danger: #ee494c;
|
||||
|
||||
--color-default: theme("colors.stone.700");
|
||||
--color-highlight: theme("colors.stone.900");
|
||||
--color-strong: theme("colors.stone.800");
|
||||
--color-muted: theme("colors.stone.300");
|
||||
|
||||
--color-transparent-white: rgba(255, 255, 255, 0.1);
|
||||
--color-transparent-primary: lch(from var(--color-primary-dark) l c h / 0.1);
|
||||
--color-transparent-primary: lch(from var(--color-primary) l c h / 0.1);
|
||||
|
||||
--color-border-default: theme("colors.stone.200");
|
||||
--color-background-highlight: lch(from var(--color-primary) l c h / 0.25);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-secondary: var(--color-primary-light);
|
||||
|
||||
--color-default: theme("colors.stone.400");
|
||||
--color-highlight: theme("colors.white");
|
||||
--color-strong: theme("colors.stone.100");
|
||||
--color-muted: theme("colors.stone.700");
|
||||
|
||||
--color-transparent-primary: lch(from var(--color-primary-light) l c h / 0.2);
|
||||
--color-transparent-primary: lch(from var(--color-primary) l c h / 0.3);
|
||||
|
||||
--color-border-default: theme("colors.stone.900");
|
||||
--color-background-highlight: lch(from var(--color-primary) l c h / 0.5);
|
||||
}
|
||||
|
||||
*:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import { ThemeProvider } from "../components/organisms/ThemeProvider";
|
||||
import { fontClasses } from "../fonts";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -13,7 +15,7 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en" className="h-full">
|
||||
<html lang="en" className="h-full" suppressHydrationWarning>
|
||||
<body
|
||||
className={[
|
||||
...fontClasses,
|
||||
@@ -21,7 +23,15 @@ export default function RootLayout({
|
||||
"bg-white dark:bg-stone-950 text-default",
|
||||
].join(" ")}
|
||||
>
|
||||
{children}
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<Toaster position="bottom-right" />
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -1,74 +1,14 @@
|
||||
import { Prose } from "@components/molecules/Prose";
|
||||
import { NewsletterForm } from "@components/organisms/NewsletterForm";
|
||||
import { ViewsLayout } from "./views/ViewsLayout";
|
||||
import Colors from "./views/colors/page";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="container 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>
|
||||
|
||||
<h2>Newsletter Subscription Form</h2>
|
||||
|
||||
<div className="p-3 border">
|
||||
<NewsletterForm />
|
||||
<main>
|
||||
<div className="col-span-8 overflow-y-scroll">
|
||||
<ViewsLayout>
|
||||
<h1 className="text-2xl font-bold mt-4">Colors</h1>
|
||||
<Colors />
|
||||
</ViewsLayout>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
18
homepage/design-system/src/app/views/ViewsLayout.tsx
Normal file
18
homepage/design-system/src/app/views/ViewsLayout.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { ViewsSideMenu } from "./ViewsSideMenu";
|
||||
|
||||
export function ViewsLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="container py-8 lg:py-16 relative h-full overflow-hidden flex flex-row gap-2">
|
||||
<ViewsSideMenu />
|
||||
<div className="flex-1 overflow-y-scroll overflow-x-hidden pr-8">
|
||||
<h1 className="text-2xl font-semibold font-display">
|
||||
Jazz Design System
|
||||
</h1>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
46
homepage/design-system/src/app/views/ViewsSideMenu.tsx
Normal file
46
homepage/design-system/src/app/views/ViewsSideMenu.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { Button } from "../../components/atoms/Button";
|
||||
|
||||
const designSystemTopics = [
|
||||
"Colors",
|
||||
"Typography",
|
||||
"Buttons",
|
||||
"Components",
|
||||
"Inputs",
|
||||
"Icons",
|
||||
];
|
||||
|
||||
export function ViewsSideMenu() {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
return (
|
||||
<div className={clsx("sticky top-0", mobileMenuOpen ? "w-32" : "w-7")}>
|
||||
<Button
|
||||
intent="default"
|
||||
variant="link"
|
||||
icon={mobileMenuOpen ? "close" : "chevronRight"}
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
size="sm"
|
||||
/>
|
||||
{mobileMenuOpen && (
|
||||
<div className="flex flex-col gap-2">
|
||||
{designSystemTopics.map((topic) => (
|
||||
<div key={topic}>
|
||||
<Button
|
||||
intent="default"
|
||||
variant="link"
|
||||
onClick={() => router.push(`/views/${topic.toLowerCase()}`)}
|
||||
>
|
||||
{topic}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
230
homepage/design-system/src/app/views/buttons/page.tsx
Normal file
230
homepage/design-system/src/app/views/buttons/page.tsx
Normal file
@@ -0,0 +1,230 @@
|
||||
"use client";
|
||||
import { Button } from "@/components/atoms/Button";
|
||||
import { Icon } from "@/components/atoms/Icon";
|
||||
import { Table } from "@/components/molecules/Table";
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownButton,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
} from "@/components/organisms/Dropdown";
|
||||
import { useState } from "react";
|
||||
import { Style } from "../../../utils/tailwindClassesMap";
|
||||
export default function ButtonsPage() {
|
||||
const variants = [
|
||||
"default",
|
||||
"primary",
|
||||
"tip",
|
||||
"info",
|
||||
"success",
|
||||
"warning",
|
||||
"alert",
|
||||
"danger",
|
||||
"muted",
|
||||
"strong",
|
||||
] as const;
|
||||
const [selectedVariant, setSelectedVariant] = useState<Style>("default");
|
||||
return (
|
||||
<>
|
||||
<h3 className="text-lg mt-5 mb-2 font-bold">Variants</h3>
|
||||
<p className="mb-3">
|
||||
For compatibility the shadcn/ui variants are mapped to the design
|
||||
system.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Button variant="default">default</Button>
|
||||
<Button variant="link">link</Button>
|
||||
<Button variant="ghost">ghost</Button>
|
||||
<Button variant="outline">outline</Button>
|
||||
<Button variant="secondary">secondary</Button>
|
||||
<Button variant="destructive">destructive</Button>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg mt-5 mb-2 font-bold">Intents</h3>
|
||||
<p>
|
||||
We have extended the shadcn/ui variants to include more styles via the
|
||||
intent prop.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{/* <Button intent="default">default</Button> */}
|
||||
<Button intent="primary">primary</Button>
|
||||
<Button intent="tip">tip</Button>
|
||||
<Button intent="info">info</Button>
|
||||
<Button intent="success">success</Button>
|
||||
<Button intent="warning">warning</Button>
|
||||
<Button intent="alert">alert</Button>
|
||||
<Button intent="danger">danger</Button>
|
||||
<Button intent="muted">muted</Button>
|
||||
<Button intent="strong">strong</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center w-48 mt-10">
|
||||
<h3 className="text-lg font-bold min-w-52">Variants & Intents</h3>
|
||||
<div className="max-w-xs ml-3">
|
||||
<Dropdown>
|
||||
<DropdownButton
|
||||
className="w-full justify-between"
|
||||
as={Button}
|
||||
intent="default"
|
||||
variant="inverted"
|
||||
>
|
||||
{selectedVariant}
|
||||
<Icon name="chevronDown" size="sm" />
|
||||
</DropdownButton>
|
||||
<DropdownMenu>
|
||||
{variants.map((variant) => (
|
||||
<DropdownItem
|
||||
key={variant}
|
||||
onClick={() => setSelectedVariant(variant)}
|
||||
>
|
||||
{variant}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-sm mt-2 mb-5">
|
||||
<strong>NB:</strong> Variants and styles are interchangeable. See the
|
||||
intent on each variant with the dropdown
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Button intent={selectedVariant} variant="outline">
|
||||
outline
|
||||
</Button>
|
||||
<Button intent={selectedVariant} variant="inverted">
|
||||
inverted
|
||||
</Button>
|
||||
<Button intent={selectedVariant} variant="ghost">
|
||||
ghost
|
||||
</Button>
|
||||
<Button intent={selectedVariant} variant="link">
|
||||
link
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-bold mt-5">Icons</h3>
|
||||
|
||||
<p>Buttons can also contain an icon and text.</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Button
|
||||
icon="delete"
|
||||
intent="danger"
|
||||
variant="link"
|
||||
iconPosition="right"
|
||||
className="col-span-2 md:col-span-1"
|
||||
>
|
||||
text danger with icon
|
||||
</Button>
|
||||
<Button
|
||||
icon="info"
|
||||
iconPosition="left"
|
||||
intent="info"
|
||||
variant="outline"
|
||||
className="col-span-2 md:col-span-1"
|
||||
>
|
||||
outline info with icon
|
||||
</Button>
|
||||
<p className="col-span-2">
|
||||
Or just use the icon prop with any of the button variants, style
|
||||
variants and colors.
|
||||
</p>
|
||||
<Button icon="newsletter" intent="tip" variant="inverted" />
|
||||
<Button icon="check" intent="success" />
|
||||
</div>
|
||||
<div className="overflow-auto">
|
||||
<h3 className="text-xl mt-5 mb-2 font-bold">Props Table</h3>
|
||||
<Table tableData={buttonPropsTableData} copyable={true} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const buttonPropsTableData = {
|
||||
headers: ["prop", "types", "default"],
|
||||
data: [
|
||||
{
|
||||
prop: "intent?",
|
||||
types: [
|
||||
"primary",
|
||||
"tip",
|
||||
"info",
|
||||
"success",
|
||||
"warning",
|
||||
"alert",
|
||||
"danger",
|
||||
"muted",
|
||||
"strong",
|
||||
],
|
||||
default: "default",
|
||||
},
|
||||
{
|
||||
prop: "variant?",
|
||||
types: [
|
||||
"default",
|
||||
"outline",
|
||||
"inverted",
|
||||
"ghost",
|
||||
"link",
|
||||
"secondary",
|
||||
"destructive",
|
||||
],
|
||||
default: "undefined",
|
||||
},
|
||||
{
|
||||
prop: "icon?",
|
||||
types: "Lucide icon name",
|
||||
default: "undefined",
|
||||
},
|
||||
{
|
||||
prop: "iconPosition?",
|
||||
types: ["left", "right"],
|
||||
default: "left",
|
||||
},
|
||||
{
|
||||
prop: "loading?",
|
||||
types: "boolean",
|
||||
default: "false",
|
||||
},
|
||||
{
|
||||
prop: "loadingText?",
|
||||
types: "string",
|
||||
default: "Loading...",
|
||||
},
|
||||
{
|
||||
prop: "disabled?",
|
||||
types: "boolean",
|
||||
default: "false",
|
||||
},
|
||||
{
|
||||
prop: "href?",
|
||||
types: "string",
|
||||
default: "undefined",
|
||||
},
|
||||
{
|
||||
prop: "newTab?",
|
||||
types: "boolean",
|
||||
default: "false",
|
||||
},
|
||||
{
|
||||
prop: "size?",
|
||||
types: ["sm", "md", "lg"],
|
||||
default: "md",
|
||||
},
|
||||
{
|
||||
prop: "className?",
|
||||
types: "string",
|
||||
default: "undefined",
|
||||
},
|
||||
{
|
||||
prop: "children?",
|
||||
types: "React.ReactNode",
|
||||
default: "undefined",
|
||||
},
|
||||
],
|
||||
};
|
||||
85
homepage/design-system/src/app/views/colors/page.tsx
Normal file
85
homepage/design-system/src/app/views/colors/page.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Colors() {
|
||||
return (
|
||||
<>
|
||||
<p className="p-1">
|
||||
Jazz uses a color palette which extends tailwind classes, with some
|
||||
modifications, see{" "}
|
||||
<Link
|
||||
href="https://tailwindcss.com/docs/colors#using-color-utilities"
|
||||
className="text-highlight"
|
||||
>
|
||||
Tailwind Color Utilities
|
||||
</Link>{" "}
|
||||
for more infomation on basic usage.
|
||||
</p>
|
||||
<p className="mt-1 p-1">
|
||||
Nearly all use cases are encapsulated by harnessing variables which have
|
||||
a baked in light & dark mode; meaning there are only a limited number of
|
||||
variables which are required for most development.
|
||||
<span className="italic">
|
||||
To see light/dark mode toggle your system settings.
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<h3 id="color-variables" className="text-md mt-6 mb-1 font-bold">
|
||||
Color Variables
|
||||
</h3>
|
||||
|
||||
<p className="mb-2 p-1">
|
||||
The following variables are available and should be used as a preference
|
||||
to tailwind classes:
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 p-3">
|
||||
<div className="bg-primary text-white p-3 rounded-md">Primary</div>
|
||||
<div className="bg-highlight text-white p-3 rounded-md">Highlight</div>
|
||||
<div className="bg-tip text-white p-3 rounded-md">Tip</div>
|
||||
<div className="bg-info text-white p-3 rounded-md">Info</div>
|
||||
<div className="bg-success text-white p-3 rounded-md">Success</div>
|
||||
<div className="bg-warning text-white p-3 rounded-md">Warning</div>
|
||||
<div className="bg-alert text-white p-3 rounded-md">Alert</div>
|
||||
<div className="bg-danger text-white p-3 rounded-md">Danger</div>
|
||||
<div className="bg-muted text-white p-3 rounded-md">Muted</div>
|
||||
<div className="bg-strong text-white dark:text-stone-900 p-3 rounded-md">
|
||||
Strong
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs mt-1 mb-4">
|
||||
NB: These classes should be used across all apps as the primary an
|
||||
secondary colour are updated per app.
|
||||
<br />
|
||||
<br />
|
||||
For full custimisation, the default colours on tailwind are programmed
|
||||
to be the tailwind semantic colours, so you can achieve a transparent
|
||||
primary with `blue/20`.
|
||||
</p>
|
||||
|
||||
<h3 id="text-color-variables" className="text-md mt-6 font-bold">
|
||||
Text Color Variables
|
||||
</h3>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 my-3 px-3">
|
||||
<ColorTypography />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const ColorTypography = () => {
|
||||
return (
|
||||
<div className={clsx("text-default rounded-md")}>
|
||||
<div className="text-default mb-1">text-default</div>
|
||||
<div className="text-muted mb-1">text-muted</div>
|
||||
<div className="text-strong mb-1">text-strong</div>
|
||||
{/* <div>
|
||||
<span className="text-default bg-highlight">bg-highlight*</span>
|
||||
</div> */}
|
||||
<div className="text-strong my-1">
|
||||
<span className="bg-highlight">text-strong bg-highlight*</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
33
homepage/design-system/src/app/views/components/page.tsx
Normal file
33
homepage/design-system/src/app/views/components/page.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import { Switch } from "@/components/atoms/Switch";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function Components() {
|
||||
const [checked, setChecked] = useState({
|
||||
md: true,
|
||||
sm: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-3">
|
||||
<div className="pb-4 flex gap-6 flex-col md:flex-row">
|
||||
<h3 className="text-md font-semibold">Switches</h3>
|
||||
<Switch
|
||||
label="Switch default (md) (Primary)"
|
||||
id="switch-md"
|
||||
checked={checked.md}
|
||||
onChange={() => setChecked({ ...checked, md: !checked.md })}
|
||||
/>
|
||||
<Switch
|
||||
label="Switch (sm) success"
|
||||
id="switch-sm"
|
||||
checked={checked.sm}
|
||||
onChange={() => setChecked({ ...checked, sm: !checked.sm })}
|
||||
size="sm"
|
||||
intent="success"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
82
homepage/design-system/src/app/views/icons/page.tsx
Normal file
82
homepage/design-system/src/app/views/icons/page.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Icon } from "@/components/atoms/Icon";
|
||||
import { Table } from "@/components/molecules/Table";
|
||||
|
||||
export default function IconsView() {
|
||||
return (
|
||||
<div className="p-3">
|
||||
<div className="flex gap-2">
|
||||
<Icon name="search" size="xs" intent="primary" />
|
||||
<Icon name="zip" size="md" intent="info" />
|
||||
<Icon name="docs" size="lg" intent="success" />
|
||||
<Icon name="file" size="xl" intent="warning" />
|
||||
<Icon name="hash" size="2xl" intent="danger" />
|
||||
<Icon name="help" size="3xl" intent="alert" />
|
||||
<Icon name="image" size="4xl" intent="tip" />
|
||||
<Icon name="corecord" size="5xl" intent="default" />
|
||||
<Icon name="corecord" size="6xl" intent="muted" />
|
||||
<Icon name="corecord" size="7xl" intent="strong" />
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Icon name="search" size="xs" intent="primary" hasBackground />
|
||||
<Icon name="zip" size="md" intent="info" hasBackground />
|
||||
<Icon name="docs" size="lg" intent="success" hasBackground />
|
||||
<Icon name="file" size="xl" intent="warning" hasBackground />
|
||||
<Icon name="hash" size="2xl" intent="danger" hasBackground />
|
||||
<Icon name="help" size="3xl" intent="alert" hasBackground />
|
||||
<Icon name="image" size="4xl" intent="tip" hasBackground />
|
||||
<Icon name="corecord" size="5xl" intent="default" hasBackground />
|
||||
<Icon name="corecord" size="6xl" intent="muted" hasBackground />
|
||||
<Icon name="corecord" size="7xl" intent="strong" hasBackground />
|
||||
</div>
|
||||
<Table className="mt-6" tableData={iconPropsTable} copyable />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const iconPropsTable = {
|
||||
headers: ["prop", "types", "default"],
|
||||
data: [
|
||||
{
|
||||
prop: "name",
|
||||
types: "string",
|
||||
default: "undefined",
|
||||
},
|
||||
{
|
||||
prop: "icon",
|
||||
types: "LucideIcon",
|
||||
default: "undefined",
|
||||
},
|
||||
{
|
||||
prop: "size",
|
||||
types: ["xs", "sm", "md", "lg", "xl", "2xl", "3xl", "4xl", "5xl"],
|
||||
default: "md",
|
||||
},
|
||||
{
|
||||
prop: "intent",
|
||||
types: [
|
||||
"default",
|
||||
"primary",
|
||||
"info",
|
||||
"success",
|
||||
"warning",
|
||||
"danger",
|
||||
"alert",
|
||||
"tip",
|
||||
"muted",
|
||||
"strong",
|
||||
"white",
|
||||
],
|
||||
default: "default",
|
||||
},
|
||||
{
|
||||
prop: "hasBackground",
|
||||
types: "boolean",
|
||||
default: "false",
|
||||
},
|
||||
{
|
||||
prop: "className",
|
||||
types: "string",
|
||||
default: "undefined",
|
||||
},
|
||||
],
|
||||
};
|
||||
146
homepage/design-system/src/app/views/inputs/page.tsx
Normal file
146
homepage/design-system/src/app/views/inputs/page.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
"use client";
|
||||
|
||||
import { Input } from "@/components/molecules/Input";
|
||||
import { InputWithButton } from "@/components/molecules/InputWithButton";
|
||||
import { Table } from "@/components/molecules/Table";
|
||||
import { NewsletterForm } from "@/components/organisms/NewsletterForm";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function InputsView() {
|
||||
const [checked, setChecked] = useState({
|
||||
md: true,
|
||||
sm: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-3">
|
||||
<p>
|
||||
Inputs consist of a combintion of atoms which can be used to create a
|
||||
variety of inputs. These atoms include:
|
||||
<br />
|
||||
<br />
|
||||
<code>Icon</code>, <code>Label</code> and <code>Button</code>, and also
|
||||
may be styled with the <code>variant</code> prop.
|
||||
</p>
|
||||
<div className="flex flex-col gap-2 mt-3">
|
||||
<h3 className="text-lg font-semibold my-2">Icons</h3>
|
||||
<Input
|
||||
icon="search"
|
||||
label="Search [label hidden]"
|
||||
iconPosition="left"
|
||||
placeholder="Search"
|
||||
labelHidden={true}
|
||||
/>
|
||||
<Input
|
||||
icon="check"
|
||||
label="Email"
|
||||
iconPosition="left"
|
||||
placeholder="Email"
|
||||
/>
|
||||
<Input
|
||||
icon="file"
|
||||
label="Password"
|
||||
iconPosition="right"
|
||||
placeholder="Password"
|
||||
/>
|
||||
<Input
|
||||
icon="eye"
|
||||
label="Password"
|
||||
iconPosition="right"
|
||||
labelPosition="row"
|
||||
placeholder="Password"
|
||||
/>
|
||||
<h3 className="text-lg font-semibold my-2">Variants</h3>
|
||||
<Input label="Muted" placeholder="Muted" intent="muted" />
|
||||
<Input label="Strong" placeholder="Strong" intent="strong" />
|
||||
<Input label="Default" placeholder="Default" intent="default" />
|
||||
<h3 className="text-lg font-semibold my-2">Buttons</h3>
|
||||
<InputWithButton
|
||||
inputProps={{
|
||||
label: "Input with button [label visible]",
|
||||
labelHidden: false,
|
||||
placeholder: "Input with button",
|
||||
intent: "success",
|
||||
}}
|
||||
buttonProps={{
|
||||
children: "Let's go",
|
||||
intent: "success",
|
||||
variant: "inverted",
|
||||
icon: "check",
|
||||
iconPosition: "left",
|
||||
}}
|
||||
/>
|
||||
<InputWithButton
|
||||
inputProps={{
|
||||
label: "Input with button [label visible]",
|
||||
labelHidden: false,
|
||||
labelPosition: "row",
|
||||
placeholder: "Input with button",
|
||||
}}
|
||||
buttonProps={{
|
||||
children: "Learn more",
|
||||
intent: "tip",
|
||||
variant: "outline",
|
||||
icon: "corecord",
|
||||
iconPosition: "right",
|
||||
}}
|
||||
/>
|
||||
<NewsletterForm />
|
||||
</div>
|
||||
<Table className="mt-6" tableData={inputPropsTable} copyable />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const inputPropsTable = {
|
||||
headers: ["prop", "types", "default"],
|
||||
data: [
|
||||
{
|
||||
prop: "label",
|
||||
types: "string",
|
||||
default: "undefined",
|
||||
},
|
||||
{
|
||||
prop: "labelHidden?",
|
||||
types: "boolean",
|
||||
default: "false",
|
||||
},
|
||||
{
|
||||
prop: "labelPosition?",
|
||||
types: ["column", "row"],
|
||||
default: "column",
|
||||
},
|
||||
{
|
||||
prop: "icon?",
|
||||
types: ["LucideIcon"],
|
||||
default: "undefined",
|
||||
},
|
||||
{
|
||||
prop: "iconPosition?",
|
||||
types: ["left", "right"],
|
||||
default: "left",
|
||||
},
|
||||
{
|
||||
prop: "intent?",
|
||||
types: [
|
||||
"primary",
|
||||
"secondary",
|
||||
"info",
|
||||
"success",
|
||||
"warning",
|
||||
"danger",
|
||||
"alert",
|
||||
"tip",
|
||||
"muted",
|
||||
"strong",
|
||||
"default",
|
||||
],
|
||||
default: "default",
|
||||
},
|
||||
{
|
||||
prop: "buttonProps?",
|
||||
types: "see Button Props",
|
||||
default: "undefined",
|
||||
},
|
||||
],
|
||||
};
|
||||
16
homepage/design-system/src/app/views/layout.tsx
Normal file
16
homepage/design-system/src/app/views/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { usePathname } from "next/navigation";
|
||||
import { ViewsLayout } from "./ViewsLayout";
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname();
|
||||
const viewName = pathname.split("/").pop();
|
||||
|
||||
return (
|
||||
<ViewsLayout>
|
||||
<h2 className="text-2xl font-bold capitalize my-3">{viewName}</h2>
|
||||
{children}
|
||||
</ViewsLayout>
|
||||
);
|
||||
}
|
||||
56
homepage/design-system/src/app/views/typography/page.tsx
Normal file
56
homepage/design-system/src/app/views/typography/page.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Prose } from "@/components/molecules/Prose";
|
||||
|
||||
export default function Typography() {
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<div>
|
||||
Heading 1
|
||||
<Prose className="p-3">
|
||||
<h1>Ship top-tier apps at high tempo</h1>
|
||||
</Prose>
|
||||
</div>
|
||||
<div>
|
||||
Heading 2
|
||||
<Prose className="p-3">
|
||||
<h2>Ship top-tier apps at high tempo</h2>
|
||||
</Prose>
|
||||
</div>
|
||||
<div>
|
||||
Heading 3
|
||||
<Prose className="p-3">
|
||||
<h3>Ship top-tier apps at high tempo</h3>
|
||||
</Prose>
|
||||
</div>
|
||||
<div>
|
||||
Heading 4
|
||||
<Prose className="p-3">
|
||||
<h4>Ship top-tier apps at high tempo</h4>
|
||||
</Prose>
|
||||
</div>
|
||||
<div>
|
||||
Paragraph
|
||||
<p className="text-xs text-highlight my-1">
|
||||
NB: That text can be styled with colour classes, including{" "}
|
||||
<code>text-muted</code> and <code>text-highlight</code>, see{" "}
|
||||
<a href="#text-color-variables">Text Color Variables</a>.
|
||||
</p>
|
||||
<Prose className="p-3">
|
||||
<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>
|
||||
Code
|
||||
<Prose className="p-3">
|
||||
This is a one-line <code>piece of code</code>
|
||||
</Prose>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +1,42 @@
|
||||
import { clsx } from "clsx";
|
||||
import Link from "next/link";
|
||||
import { forwardRef } from "react";
|
||||
import {
|
||||
Style,
|
||||
Variant,
|
||||
VariantColor,
|
||||
colorToBgActiveMap25,
|
||||
colorToBgActiveMap50,
|
||||
colorToBgHoverMap10,
|
||||
colorToBgHoverMap30,
|
||||
colorToBgMap,
|
||||
shadowClassesBase,
|
||||
sizeClasses,
|
||||
styleToBgGradientColorMap,
|
||||
styleToBgGradientHoverMap,
|
||||
styleToBgTransparentActiveMap,
|
||||
styleToBorderMap,
|
||||
styleToButtonStateMap,
|
||||
styleToColorMap,
|
||||
styleToHoverShadowMap,
|
||||
styleToTextActiveMap,
|
||||
styleToTextHoverMap,
|
||||
styleToTextMap,
|
||||
} from "../../utils/tailwindClassesMap";
|
||||
import { Icon } from "./Icon";
|
||||
import type { IconName } from "./Icon";
|
||||
import { Spinner } from "./Spinner";
|
||||
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: "primary" | "secondary" | "tertiary" | "destructive" | "plain";
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
intent?: Style;
|
||||
variant?: Variant;
|
||||
state?: "hover" | "active" | "focus" | "disabled";
|
||||
size?: "sm" | "md" | "lg";
|
||||
href?: string;
|
||||
newTab?: boolean;
|
||||
icon?: IconName;
|
||||
iconPosition?: "left" | "right" | "center";
|
||||
loading?: boolean;
|
||||
loadingText?: string;
|
||||
children?: React.ReactNode;
|
||||
@@ -18,63 +44,44 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
function ButtonIcon({ icon, loading }: ButtonProps) {
|
||||
if (!Icon) return null;
|
||||
|
||||
const className = "size-5";
|
||||
|
||||
if (loading) return <Spinner className={className} />;
|
||||
|
||||
if (icon) {
|
||||
return <Icon name={icon} className={className} />;
|
||||
}
|
||||
}
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
children,
|
||||
size = "md",
|
||||
variant = "primary",
|
||||
intent = "default",
|
||||
variant,
|
||||
href,
|
||||
disabled,
|
||||
newTab,
|
||||
loading,
|
||||
loadingText,
|
||||
icon,
|
||||
iconPosition = "left",
|
||||
type = "button",
|
||||
...buttonProps
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const sizeClasses = {
|
||||
sm: "text-sm py-1 px-2",
|
||||
md: "py-1.5 px-3",
|
||||
lg: "md:text-lg py-2 px-3 md:px-8 md:py-3",
|
||||
const styleClass =
|
||||
styleClasses(intent, variant)[variant as keyof typeof styleClasses] || "";
|
||||
|
||||
const getClasses = ({ variant }: { variant: string | undefined }) => {
|
||||
return {
|
||||
[sizeClasses[size as keyof typeof sizeClasses]]: size,
|
||||
[variantClass(intent)]: !variant,
|
||||
[styleClass]: variant,
|
||||
};
|
||||
};
|
||||
|
||||
const variantClasses = {
|
||||
primary:
|
||||
"bg-primary border border-primary text-white font-medium hover:bg-highlight hover:border-primary hover:text-primary dark:hover:bg-highlight dark:hover:text-primary",
|
||||
secondary:
|
||||
"text-stone-900 border font-medium hover:border-primary hover:text-primary hover:bg-highlight hover:dark:border-primary dark:text-white dark:hover:text-primary",
|
||||
tertiary: "text-primary underline underline-offset-4",
|
||||
destructive:
|
||||
"bg-red-600 border-red-600 text-white font-medium hover:bg-red-700 hover:border-red-700",
|
||||
};
|
||||
|
||||
const classNames =
|
||||
variant === "plain"
|
||||
? className
|
||||
: clsx(
|
||||
className,
|
||||
"inline-flex items-center justify-center gap-2 rounded-lg text-center transition-colors",
|
||||
"disabled:pointer-events-none disabled:opacity-70",
|
||||
sizeClasses[size],
|
||||
variantClasses[variant],
|
||||
disabled && "opacity-50 cursor-not-allowed pointer-events-none",
|
||||
);
|
||||
const classNames = clsx(
|
||||
"inline-flex items-center justify-center gap-2 rounded-lg text-center transition-colors w-fit text-nowrap",
|
||||
getClasses({ variant }),
|
||||
"disabled:pointer-events-none disabled:opacity-70",
|
||||
disabled && "opacity-50 cursor-not-allowed pointer-events-none",
|
||||
className,
|
||||
);
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
@@ -83,10 +90,17 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
target={newTab ? "_blank" : undefined}
|
||||
className={classNames}
|
||||
>
|
||||
<ButtonIcon icon={icon} loading={loading} />
|
||||
{icon && (
|
||||
<Icon
|
||||
name={icon}
|
||||
className={`size-5 ${iconPosition === "left" ? "mr-2" : iconPosition === "right" ? "ml-2" : ""}, ${iconVariant(intent, variant)}`}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
{newTab ? (
|
||||
<span className="inline-block text-muted relative -top-0.5 -left-2 -mr-2">
|
||||
<span
|
||||
className={`inline-block relative -top-0.5 -left-2 -mr-2 ${styleToTextMap[intent as keyof typeof styleToTextMap]}`}
|
||||
>
|
||||
⌝
|
||||
</span>
|
||||
) : (
|
||||
@@ -104,10 +118,49 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
className={classNames}
|
||||
type={type}
|
||||
>
|
||||
<ButtonIcon icon={icon} loading={loading} />
|
||||
|
||||
{loading ? (
|
||||
<Spinner className="size-5" />
|
||||
) : (
|
||||
icon &&
|
||||
iconPosition === "left" && (
|
||||
<Icon name={icon} intent={iconVariant(intent, variant)} />
|
||||
)
|
||||
)}
|
||||
{loading && loadingText ? loadingText : children}
|
||||
{icon && iconPosition === "right" && (
|
||||
<Icon
|
||||
name={icon}
|
||||
intent={iconVariant(intent, variant)}
|
||||
hasHover={true}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const iconVariant = (intent: Style, variant: Variant | undefined) => {
|
||||
return variant ? intent : intent === "default" ? "default" : "white";
|
||||
};
|
||||
const textColorVariant = (style: Style) => {
|
||||
return style === "default"
|
||||
? "text-stone-700 dark:text-white hover:text-stone-800 active:text-stone-700 dark:hover:text-stone-100 dark:active:text-stone-200"
|
||||
: style === "strong"
|
||||
? "text-stone-100 dark:text-stone-900"
|
||||
: "text-white";
|
||||
};
|
||||
|
||||
const variantClass = (intent: Style) =>
|
||||
`${styleToBgGradientColorMap[intent]} ${styleToBgGradientHoverMap[intent]} ${textColorVariant(intent)} ${styleToButtonStateMap[intent]} ${shadowClassesBase} shadow-stone-400/20`;
|
||||
|
||||
const styleClasses = (intent: Style, variant: Variant | undefined) => {
|
||||
return {
|
||||
outline: `border ${styleToBorderMap[intent]} ${styleToTextMap[intent]} ${styleToTextHoverMap[intent]} ${styleToHoverShadowMap[intent]} ${styleToBgTransparentActiveMap[intent]} shadow-[5px_0px]`,
|
||||
inverted: `${styleToTextMap[intent]} ${colorToBgHoverMap30[styleToColorMap[intent] as VariantColor]} ${colorToBgMap[styleToColorMap[intent] as VariantColor]} ${colorToBgActiveMap50[styleToColorMap[intent] as VariantColor]} ${shadowClassesBase}`,
|
||||
ghost: `bg-transparent ${styleToTextMap[intent]} ${colorToBgHoverMap10[styleToColorMap[intent] as VariantColor]} ${colorToBgActiveMap25[styleToColorMap[intent] as VariantColor]}`,
|
||||
link: `bg-transparent ${styleToTextMap[intent]} underline underline-offset-2 p-0 hover:bg-transparent ${styleToTextHoverMap[intent]} ${styleToTextActiveMap[intent]} active:underline-stone-500`,
|
||||
secondary: `bg-stone-300 ${styleToTextMap[intent]} hover:bg-stone-400/80 active:bg-stone-500/80`,
|
||||
destructive: `bg-danger text-white hover:bg-red/80 active:bg-red/70`,
|
||||
default: `${styleToBgGradientColorMap["default"]} ${styleToBgGradientHoverMap["default"]} ${textColorVariant("default")} ${styleToButtonStateMap["default"]} ${shadowClassesBase} shadow-stone-400/20`,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
ChevronRightIcon,
|
||||
ClipboardIcon,
|
||||
CodeIcon,
|
||||
Eye,
|
||||
FileLock2Icon,
|
||||
FileTextIcon,
|
||||
FingerprintIcon,
|
||||
@@ -46,9 +47,15 @@ import {
|
||||
XIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
import clsx from "clsx";
|
||||
import {
|
||||
Style,
|
||||
styleToTextHoverMap,
|
||||
styleToTextMap,
|
||||
} from "../../utils/tailwindClassesMap";
|
||||
import { GcmpIcons } from "./icons";
|
||||
|
||||
const icons = {
|
||||
export const icons = {
|
||||
arrowDown: ArrowDownIcon,
|
||||
arrowRight: ArrowRightIcon,
|
||||
auth: UserIcon,
|
||||
@@ -100,6 +107,7 @@ const icons = {
|
||||
// text editor icons
|
||||
bold: BoldIcon,
|
||||
italic: ItalicIcon,
|
||||
eye: Eye,
|
||||
};
|
||||
|
||||
// copied from tailwind line height https://tailwindcss.com/docs/font-size
|
||||
@@ -107,8 +115,8 @@ const sizes = {
|
||||
"2xs": 14,
|
||||
xs: 16,
|
||||
sm: 20,
|
||||
md: 24,
|
||||
lg: 28,
|
||||
md: 22,
|
||||
lg: 26,
|
||||
xl: 28,
|
||||
"2xl": 32,
|
||||
"3xl": 36,
|
||||
@@ -143,13 +151,19 @@ export function Icon({
|
||||
name,
|
||||
icon,
|
||||
size = "md",
|
||||
intent = "default",
|
||||
hasBackground = false,
|
||||
className,
|
||||
hasHover = false,
|
||||
...svgProps
|
||||
}: {
|
||||
name?: IconName;
|
||||
icon?: LucideIcon;
|
||||
size?: keyof typeof sizes;
|
||||
intent?: Style | "white";
|
||||
hasBackground?: boolean;
|
||||
className?: string;
|
||||
hasHover?: boolean;
|
||||
} & React.SVGProps<SVGSVGElement>) {
|
||||
if (!icon && (!name || !icons.hasOwnProperty(name))) {
|
||||
throw new Error(`Icon not found: ${name}`);
|
||||
@@ -158,13 +172,52 @@ export function Icon({
|
||||
// @ts-ignore
|
||||
const IconComponent = icons?.hasOwnProperty(name) ? icons[name] : icon;
|
||||
|
||||
const iconClass = {
|
||||
...styleToTextMap,
|
||||
white: "text-white",
|
||||
};
|
||||
|
||||
const iconHoverClass = {
|
||||
...styleToTextHoverMap,
|
||||
white: "hover:text-white/90",
|
||||
};
|
||||
|
||||
const backgroundClasses = {
|
||||
default: "bg-stone-200/30 dark:bg-stone-900/30",
|
||||
primary: "bg-primary-transparent",
|
||||
secondary: "bg-secondary-transparent",
|
||||
info: "bg-info-transparent",
|
||||
success: "bg-success-transparent",
|
||||
warning: "bg-warning-transparent",
|
||||
danger: "bg-danger-transparent",
|
||||
alert: "bg-alert-transparent",
|
||||
tip: "bg-tip-transparent",
|
||||
muted: "bg-stone-300/30 dark:bg-stone-700/30",
|
||||
strong: "bg-stone-900/30 dark:bg-stone-100/30",
|
||||
};
|
||||
|
||||
const roundedClasses = {
|
||||
xs: "rounded-xs",
|
||||
sm: "rounded-sm",
|
||||
md: "rounded-md",
|
||||
lg: "rounded-lg",
|
||||
xl: "rounded-xl",
|
||||
};
|
||||
|
||||
return (
|
||||
<IconComponent
|
||||
aria-hidden="true"
|
||||
size={sizes[size]}
|
||||
strokeWidth={strokeWidths[size]}
|
||||
strokeLinecap="round"
|
||||
className={className}
|
||||
className={clsx(
|
||||
roundedClasses[size as keyof typeof roundedClasses] || "rounded-lg",
|
||||
iconClass[intent as keyof typeof iconClass],
|
||||
hasBackground &&
|
||||
backgroundClasses[intent as keyof typeof backgroundClasses],
|
||||
hasHover && iconHoverClass[intent as keyof typeof iconHoverClass],
|
||||
className,
|
||||
)}
|
||||
{...svgProps}
|
||||
/>
|
||||
);
|
||||
|
||||
13
homepage/design-system/src/components/atoms/Label.tsx
Normal file
13
homepage/design-system/src/components/atoms/Label.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Label as LabelRadix } from "radix-ui";
|
||||
|
||||
export function Label({
|
||||
label,
|
||||
htmlFor,
|
||||
className,
|
||||
}: { label: string; htmlFor: string; className?: string }) {
|
||||
return (
|
||||
<LabelRadix.Root className={className} htmlFor={htmlFor}>
|
||||
{label}
|
||||
</LabelRadix.Root>
|
||||
);
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export function Spinner({ className }: { className?: string }) {
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
strokeWidth="4"
|
||||
></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
import clsx from "clsx";
|
||||
import { Switch as RadixSwitch } from "radix-ui";
|
||||
import { Style, styleToBgMap } from "../../utils/tailwindClassesMap";
|
||||
|
||||
export function Switch({
|
||||
id,
|
||||
size = "sm",
|
||||
size = "md",
|
||||
intent = "primary",
|
||||
checked,
|
||||
onChange,
|
||||
label,
|
||||
}: {
|
||||
id: string;
|
||||
size?: "sm" | "md";
|
||||
intent?: Style;
|
||||
checked: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
label?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 w-content">
|
||||
<div className="flex items-center w-content">
|
||||
<label
|
||||
className={clsx("text-xs text-gray-500", labelSizeClass[size])}
|
||||
className={clsx("text-gray-500 mr-2", labelSizeClass[size])}
|
||||
htmlFor={id}
|
||||
>
|
||||
{label}
|
||||
@@ -25,18 +28,21 @@ export function Switch({
|
||||
<RadixSwitch.Root
|
||||
id={id}
|
||||
className={clsx(
|
||||
"min-w-10 h-6 rounded-full relative",
|
||||
size === "sm" && "min-w-6 h-3.5",
|
||||
checked ? "bg-primary" : "bg-stone-200",
|
||||
"rounded-full relative",
|
||||
size === "sm" ? "min-w-6 h-4" : "min-w-10 h-6",
|
||||
checked ? styleToBgMap[intent] : "bg-stone-200",
|
||||
)}
|
||||
checked={checked}
|
||||
onCheckedChange={onChange}
|
||||
>
|
||||
<RadixSwitch.Thumb
|
||||
className={clsx(
|
||||
"block w-4 h-4 bg-white rounded-full transition-transform duration-300 translate-x-0 ml-[0.06rem]",
|
||||
size === "sm" && "w-3 h-3",
|
||||
checked && "translate-x-[0.6rem]",
|
||||
"block bg-white rounded-full transition-transform duration-300 translate-x-0 ml-[0.1em]",
|
||||
size === "sm" ? "w-3 h-3" : "w-5 h-5",
|
||||
checked &&
|
||||
(size === "sm"
|
||||
? "translate-x-[0.5rem]"
|
||||
: "translate-x-[1.01rem]"),
|
||||
)}
|
||||
/>
|
||||
</RadixSwitch.Root>
|
||||
|
||||
@@ -21,7 +21,9 @@ export function FeatureCard({
|
||||
{icon && (
|
||||
<Icon
|
||||
name={icon}
|
||||
className="text-primary p-1.5 rounded-lg bg-blue-50 dark:bg-stone-900 mb-2.5"
|
||||
intent="primary"
|
||||
hasBackground
|
||||
className="p-1.5 rounded-lg mb-2.5"
|
||||
size="3xl"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,16 +1,47 @@
|
||||
import { clsx } from "clsx";
|
||||
import { forwardRef, useId } from "react";
|
||||
import { Style, styleToActiveBorderMap } from "../../utils/tailwindClassesMap";
|
||||
import { Button, ButtonProps } from "../atoms/Button";
|
||||
import { Icon, icons } from "../atoms/Icon";
|
||||
import { Label } from "../atoms/Label";
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
// label can be hidden with a "label:sr-only" className
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
label: string;
|
||||
className?: string;
|
||||
id?: string;
|
||||
placeholder?: string;
|
||||
icon?: keyof typeof icons;
|
||||
iconPosition?: "left" | "right";
|
||||
labelHidden?: boolean;
|
||||
labelPosition?: "column" | "row";
|
||||
button?: ButtonProps;
|
||||
intent?: Style;
|
||||
}
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
({ label, className, id: customId, ...inputProps }, ref) => {
|
||||
(
|
||||
{
|
||||
label,
|
||||
className,
|
||||
id: customId,
|
||||
placeholder,
|
||||
icon,
|
||||
iconPosition = "left",
|
||||
labelHidden,
|
||||
labelPosition,
|
||||
button,
|
||||
intent = "default",
|
||||
...inputProps
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const generatedId = useId();
|
||||
const id = customId || generatedId;
|
||||
const inputIconClassName =
|
||||
icon && iconPosition === "left"
|
||||
? "pl-9"
|
||||
: icon && iconPosition === "right";
|
||||
|
||||
const inputClassName = clsx(
|
||||
"w-full rounded-md border px-3.5 py-2 shadow-sm",
|
||||
@@ -18,15 +49,53 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
"dark:text-white dark:bg-stone-925",
|
||||
);
|
||||
|
||||
const containerClassName = clsx("grid gap-1", className);
|
||||
|
||||
return (
|
||||
<div className={containerClassName}>
|
||||
<label htmlFor={id} className="text-stone-600 dark:text-stone-300">
|
||||
{label}
|
||||
</label>
|
||||
|
||||
<input ref={ref} {...inputProps} id={id} className={inputClassName} />
|
||||
<div
|
||||
className={clsx(
|
||||
"relative w-full",
|
||||
labelPosition === "row" ? "flex flex-row items-center" : "",
|
||||
)}
|
||||
>
|
||||
<Label
|
||||
label={label}
|
||||
htmlFor={id}
|
||||
className={clsx(
|
||||
labelPosition === "row" ? "mr-2" : "w-full",
|
||||
labelHidden ? "sr-only" : "",
|
||||
)}
|
||||
/>
|
||||
<div className={clsx("flex gap-2 w-full items-center")}>
|
||||
<input
|
||||
ref={ref}
|
||||
{...inputProps}
|
||||
id={id}
|
||||
className={clsx(
|
||||
inputClassName,
|
||||
inputIconClassName,
|
||||
className,
|
||||
"px-2",
|
||||
styleToActiveBorderMap[
|
||||
intent as keyof typeof styleToActiveBorderMap
|
||||
],
|
||||
)}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
{icon && (
|
||||
<Icon
|
||||
name={icon}
|
||||
className={clsx(
|
||||
"absolute",
|
||||
iconPosition === "left"
|
||||
? "left-2"
|
||||
: iconPosition === "right"
|
||||
? "right-2"
|
||||
: "",
|
||||
)}
|
||||
intent={intent}
|
||||
/>
|
||||
)}
|
||||
{button && <Button {...button} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ButtonProps } from "../atoms/Button";
|
||||
import { Input, InputProps } from "./Input";
|
||||
|
||||
export function InputWithButton({
|
||||
inputProps,
|
||||
buttonProps,
|
||||
}: {
|
||||
inputProps: InputProps;
|
||||
buttonProps: ButtonProps;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex gap-2 w-full">
|
||||
<Input {...inputProps} button={buttonProps} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
110
homepage/design-system/src/components/molecules/Table.tsx
Normal file
110
homepage/design-system/src/components/molecules/Table.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
"use client";
|
||||
import { clsx } from "clsx";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
export interface TableProps extends React.HTMLAttributes<HTMLTableElement> {
|
||||
className?: string;
|
||||
tableData: {
|
||||
headers: string[];
|
||||
data: {
|
||||
[key: string]: string | string[];
|
||||
}[];
|
||||
};
|
||||
copyable?: boolean;
|
||||
}
|
||||
|
||||
export function Table({
|
||||
className,
|
||||
tableData,
|
||||
copyable,
|
||||
...tableProps
|
||||
}: TableProps) {
|
||||
return (
|
||||
<table
|
||||
className={clsx(
|
||||
"w-full border border-gray-200 rounded-lg overflow-hidden overflow-x-scroll",
|
||||
className,
|
||||
)}
|
||||
{...tableProps}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
{tableData.headers.map((header) => (
|
||||
<th key={header} className="text-left pl-1 capitalize">
|
||||
{header}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="border-t border-gray-200">
|
||||
{tableData.data.map((row, index) => (
|
||||
<tr
|
||||
key={`${row.id as string}-${index}=${tableData.headers.join("-")}`}
|
||||
className={clsx(
|
||||
index % 2 === 0
|
||||
? "bg-stone-200/20 dark:bg-stone-800/40 hover:bg-stone-200/70 dark:hover:bg-stone-800/90"
|
||||
: "hover:bg-stone-200/50 dark:hover:bg-stone-100/20",
|
||||
"border-b border-stone-200 text-stone-800 hover:text-black dark:text-stone-200 dark:hover:text-white",
|
||||
)}
|
||||
>
|
||||
{tableData.headers.map((header, index) => (
|
||||
<td
|
||||
key={header}
|
||||
className={clsx(
|
||||
index === 0 && "pl-1",
|
||||
typeof row[header] !== "string" && "flex",
|
||||
)}
|
||||
>
|
||||
{typeof row[header] !== "string" ? (
|
||||
<TableDataContainer>
|
||||
{row[header]?.map((item) => (
|
||||
<div
|
||||
className={clsx(
|
||||
"hover:underline",
|
||||
copyable && "cursor-pointer",
|
||||
)}
|
||||
key={item}
|
||||
onClick={() => {
|
||||
if (copyable) {
|
||||
navigator.clipboard.writeText(item.toString());
|
||||
toast.success("Copied to clipboard");
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</TableDataContainer>
|
||||
) : (
|
||||
<TableDataContainer isCopyable={copyable}>
|
||||
{row[header as keyof typeof row]}
|
||||
</TableDataContainer>
|
||||
)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
const TableDataContainer = ({
|
||||
children,
|
||||
className,
|
||||
isCopyable,
|
||||
}: { children: React.ReactNode; className?: string; isCopyable?: boolean }) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx("flex gap-2", className, isCopyable && "cursor-pointer")}
|
||||
onClick={() => {
|
||||
if (isCopyable && children) {
|
||||
navigator.clipboard.writeText(children.toString());
|
||||
toast.success("Copied to clipboard");
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -170,6 +170,7 @@ export function MobileNav({
|
||||
{item.title}
|
||||
</NavLink>
|
||||
))}
|
||||
{cta}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
@@ -200,7 +201,6 @@ export function MobileNav({
|
||||
<NavLinkLogo prominent href="/" className="mr-auto">
|
||||
{mainLogo}
|
||||
</NavLinkLogo>
|
||||
{cta}
|
||||
<button
|
||||
className="flex gap-2 p-3 rounded-xl items-center"
|
||||
onClick={() => {
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
import { useState } from "react";
|
||||
import { ErrorResponse } from "resend";
|
||||
import { subscribe } from "../../actions/resend";
|
||||
import { Button } from "../atoms/Button";
|
||||
import { Icon } from "../atoms/Icon";
|
||||
import { Input } from "../molecules/Input";
|
||||
import { InputWithButton } from "../molecules/InputWithButton";
|
||||
|
||||
export function NewsletterForm() {
|
||||
const [email, setEmail] = useState("");
|
||||
// const [subscribed, setSubscribed] = useState(false);
|
||||
const [error, setError] = useState<ErrorResponse | undefined>();
|
||||
|
||||
const [state, setState] = useState<"ready" | "loading" | "success" | "error">(
|
||||
@@ -41,32 +39,37 @@ export function NewsletterForm() {
|
||||
}
|
||||
|
||||
if (state === "error" && error?.message) {
|
||||
return <p className="text-red-700">Error: {error.message}</p>;
|
||||
return <p className="text-danger">Error: {error.message}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<form action="" onSubmit={submit} className="flex gap-x-4 w-120 max-w-md">
|
||||
<Input
|
||||
id="email-address"
|
||||
name="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
placeholder="Enter your email"
|
||||
autoComplete="email"
|
||||
className="flex-1 label:sr-only"
|
||||
label="Email address"
|
||||
<InputWithButton
|
||||
inputProps={{
|
||||
id: "email-address",
|
||||
name: "email",
|
||||
type: "email",
|
||||
value: email,
|
||||
onChange: (e) => setEmail(e.target.value),
|
||||
required: true,
|
||||
placeholder: "Enter your email",
|
||||
autoComplete: "email",
|
||||
className: "flex-1",
|
||||
label: "Email address",
|
||||
labelHidden: true,
|
||||
intent: "primary",
|
||||
}}
|
||||
buttonProps={{
|
||||
type: "submit",
|
||||
intent: "primary",
|
||||
variant: "outline",
|
||||
loadingText: "Subscribing...",
|
||||
loading: state === "loading",
|
||||
icon: "newsletter",
|
||||
iconPosition: "right",
|
||||
children: "Subscribe",
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="secondary"
|
||||
loadingText="Subscribing..."
|
||||
loading={state === "loading"}
|
||||
icon="newsletter"
|
||||
>
|
||||
Subscribe
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { ThemeProvider as NextThemesProvider, useTheme } from "next-themes";
|
||||
import { type ThemeProviderProps } from "next-themes/dist/types";
|
||||
import * as React from "react";
|
||||
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>
|
||||
);
|
||||
}
|
||||
13
homepage/design-system/src/covalues/schema.ts
Normal file
13
homepage/design-system/src/covalues/schema.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { co, z } from "jazz-tools";
|
||||
|
||||
// Example CoMap class
|
||||
export const Person = co.map({
|
||||
name: z.string(),
|
||||
age: z.number(),
|
||||
height: z.number().optional(),
|
||||
weight: z.number().optional(),
|
||||
});
|
||||
|
||||
export const ListOfPeople = co.list(Person);
|
||||
|
||||
export const PersonFeed = co.feed(Person);
|
||||
308
homepage/design-system/src/utils/tailwindClassesMap.ts
Normal file
308
homepage/design-system/src/utils/tailwindClassesMap.ts
Normal file
@@ -0,0 +1,308 @@
|
||||
export type Variant =
|
||||
| "default"
|
||||
| "secondary"
|
||||
| "destructive"
|
||||
| "ghost"
|
||||
| "outline"
|
||||
| "link"
|
||||
| "inverted";
|
||||
|
||||
export type Style =
|
||||
| "default"
|
||||
| "primary"
|
||||
| "tip"
|
||||
| "info"
|
||||
| "success"
|
||||
| "warning"
|
||||
| "alert"
|
||||
| "danger"
|
||||
| "muted"
|
||||
| "strong";
|
||||
|
||||
export const sizeClasses = {
|
||||
sm: "text-sm py-1 px-2",
|
||||
md: "py-1.5 px-3",
|
||||
lg: "md:text-lg py-2 px-3 md:px-8 md:py-3",
|
||||
};
|
||||
|
||||
export const styleToBorderMap = {
|
||||
primary: "border-primary",
|
||||
info: "border-info",
|
||||
success: "border-success",
|
||||
warning: "border-warning",
|
||||
danger: "border-danger",
|
||||
alert: "border-alert",
|
||||
tip: "border-tip",
|
||||
muted: "border-stone-200 dark:border-stone-700",
|
||||
strong: "border-stone-900 dark:border-stone-100",
|
||||
default: "border-stone-600 dark:border-stone-200",
|
||||
};
|
||||
|
||||
export const styleToActiveBorderMap = {
|
||||
primary: "active:border-primary-transparent focus:border-primary-transparent",
|
||||
info: "active:border-info-transparent focus:border-info-transparent",
|
||||
success: "active:border-success-transparent focus:border-success-transparent",
|
||||
warning: "active:border-warning-transparent focus:border-warning-transparent",
|
||||
danger: "active:border-danger-transparent focus:border-danger-transparent",
|
||||
alert: "active:border-alert-transparent focus:border-alert-transparent",
|
||||
tip: "active:border-tip-transparent focus:border-tip-transparent",
|
||||
muted:
|
||||
"active:border-stone-200/30 focus:border-stone-200/30 dark:active:border-stone-900/30 dark:focus:border-stone-900/30",
|
||||
strong:
|
||||
"active:border-stone-900/30 focus:border-stone-900/30 dark:active:border-stone-200/30 dark:focus:border-stone-200/30",
|
||||
default:
|
||||
"active:border-stone-600/30 dark:active:border-stone-100/30 focus:border-stone-600/30 dark:focus:border-stone-100/30",
|
||||
};
|
||||
|
||||
export const styleToBgMap = {
|
||||
primary: "bg-primary",
|
||||
info: "bg-info",
|
||||
success: "bg-success",
|
||||
warning: "bg-warning",
|
||||
danger: "bg-danger",
|
||||
alert: "bg-alert",
|
||||
tip: "bg-tip",
|
||||
muted: "bg-stone-200 dark:bg-stone-900",
|
||||
strong: "bg-stone-900 dark:bg-stone-200",
|
||||
default: "bg-stone-700 dark:bg-stone-100",
|
||||
};
|
||||
|
||||
export const styleToBgTransparentHoverMap = {
|
||||
primary: "hover:bg-primary-transparent",
|
||||
info: "hover:bg-info-transparent",
|
||||
success: "hover:bg-success-transparent",
|
||||
warning: "hover:bg-warning-transparent",
|
||||
danger: "hover:bg-danger-transparent",
|
||||
alert: "hover:bg-alert-transparent",
|
||||
tip: "hover:bg-tip-transparent",
|
||||
muted: "hover:bg-stone-100/20 dark:hover:bg-stone-900/20",
|
||||
strong: "hover:bg-stone-900/20 dark:hover:bg-stone-100/20",
|
||||
default: "hover:bg-stone-600/20 dark:hover:bg-stone-100/20",
|
||||
};
|
||||
|
||||
export const styleToBgTransparentActiveMap = {
|
||||
primary: "active:bg-blue/20",
|
||||
info: "active:bg-purple/20",
|
||||
success: "active:bg-green/20",
|
||||
warning: "active:bg-orange/20",
|
||||
danger: "active:bg-red/20",
|
||||
alert: "active:bg-yellow/20",
|
||||
tip: "active:bg-cyan/20",
|
||||
muted: "active:bg-stone-400/20",
|
||||
strong: "active:bg-stone-900/20",
|
||||
default: "active:bg-stone-600/20 dark:active:bg-stone-100/20",
|
||||
};
|
||||
|
||||
export const styleToTextMap = {
|
||||
primary: "text-primary",
|
||||
info: "text-info",
|
||||
success: "text-success",
|
||||
warning: "text-warning",
|
||||
danger: "text-danger",
|
||||
alert: "text-alert",
|
||||
tip: "text-tip",
|
||||
muted: "text-stone-500 dark:text-stone-400",
|
||||
strong: "text-stone-900 dark:text-white",
|
||||
default: "text-stone-700 dark:text-stone-100",
|
||||
};
|
||||
|
||||
export const styleToTextHoverMap = {
|
||||
primary: "hover:text-primary-light",
|
||||
info: "hover:text-info-light",
|
||||
success: "hover:text-success-light",
|
||||
warning: "hover:text-warning-light",
|
||||
danger: "hover:text-danger-light",
|
||||
alert: "hover:text-alert-light",
|
||||
tip: "hover:text-tip-light",
|
||||
muted: "hover:text-stone-400 dark:hover:text-stone-500",
|
||||
strong: "hover:text-stone-700 dark:hover:text-stone-300",
|
||||
default: "hover:text-stone-600 dark:hover:text-stone-200",
|
||||
};
|
||||
|
||||
export const styleToTextActiveMap = {
|
||||
primary: "active:text-primary-dark",
|
||||
info: "active:text-info-dark",
|
||||
success: "active:text-success-dark",
|
||||
warning: "active:text-warning-dark",
|
||||
danger: "active:text-danger-dark",
|
||||
alert: "active:text-alert-dark",
|
||||
tip: "active:text-tip-dark",
|
||||
muted: "active:text-stone-400 dark:active:text-stone-500",
|
||||
strong: "active:text-stone-700 dark:active:text-stone-300",
|
||||
default: "active:text-stone-800 dark:active:text-stone-400",
|
||||
};
|
||||
|
||||
export type VariantColor =
|
||||
| "blue"
|
||||
| "indigo"
|
||||
| "purple"
|
||||
| "green"
|
||||
| "orange"
|
||||
| "red"
|
||||
| "yellow"
|
||||
| "cyan"
|
||||
| "muted"
|
||||
| "strong"
|
||||
| "default";
|
||||
|
||||
export const styleToColorMap = {
|
||||
primary: "blue",
|
||||
info: "purple",
|
||||
success: "green",
|
||||
warning: "orange",
|
||||
danger: "red",
|
||||
alert: "yellow",
|
||||
tip: "cyan",
|
||||
muted: "muted",
|
||||
strong: "strong",
|
||||
default: "default",
|
||||
};
|
||||
|
||||
export const colorToBgMap = {
|
||||
blue: "bg-blue/20",
|
||||
indigo: "bg-indigo-500/20",
|
||||
purple: "bg-purple/20",
|
||||
green: "bg-green/20",
|
||||
orange: "bg-orange/20",
|
||||
red: "bg-red/20",
|
||||
yellow: "bg-yellow/20",
|
||||
cyan: "bg-cyan/20",
|
||||
muted: "bg-stone-200/20 dark:bg-stone-900/50",
|
||||
strong: "bg-stone-900/20 dark:bg-stone-100/50",
|
||||
default: "bg-stone-600/20 dark:bg-white/20",
|
||||
};
|
||||
|
||||
export const colorToBgHoverMap30 = {
|
||||
blue: "hover:bg-blue/30",
|
||||
indigo: "hover:bg-indigo-500/30",
|
||||
purple: "hover:bg-purple/30",
|
||||
green: "hover:bg-green/30",
|
||||
orange: "hover:bg-orange/30",
|
||||
red: "hover:bg-red/30",
|
||||
yellow: "hover:bg-yellow/30",
|
||||
cyan: "hover:bg-cyan/30",
|
||||
muted: "hover:bg-stone-200/30 dark:hover:bg-stone-900/30",
|
||||
strong: "hover:bg-stone-900/30 dark:hover:bg-stone-100/30",
|
||||
default: "hover:bg-stone-600/30 dark:hover:bg-white/30",
|
||||
};
|
||||
|
||||
export const colorToBgHoverMap10 = {
|
||||
blue: "hover:bg-blue/10",
|
||||
indigo: "hover:bg-indigo-500/10",
|
||||
purple: "hover:bg-purple/10",
|
||||
green: "hover:bg-green/10",
|
||||
orange: "hover:bg-orange/10",
|
||||
red: "hover:bg-red/10",
|
||||
yellow: "hover:bg-yellow/10",
|
||||
cyan: "hover:bg-cyan/10",
|
||||
muted: "hover:bg-stone-200/30 dark:hover:bg-stone-800/30",
|
||||
strong: "hover:bg-stone-900/10 dark:hover:bg-stone-100/10",
|
||||
default: "hover:bg-stone-600/10 dark:hover:bg-white/10",
|
||||
};
|
||||
|
||||
export const colorToBgActiveMap50 = {
|
||||
blue: "active:bg-blue/50",
|
||||
indigo: "active:bg-indigo-500/50",
|
||||
purple: "active:bg-purple/50",
|
||||
green: "active:bg-green/50",
|
||||
orange: "active:bg-orange/50",
|
||||
red: "active:bg-red/50",
|
||||
yellow: "active:bg-yellow/50",
|
||||
cyan: "active:bg-cyan/50",
|
||||
muted: "active:bg-stone-100/50 dark:active:bg-stone-900/50",
|
||||
strong: "active:bg-stone-800/40 dark:active:bg-stone-200/40",
|
||||
default: "active:bg-stone-900/40 dark:active:bg-white/50",
|
||||
};
|
||||
|
||||
export const colorToBgActiveMap25 = {
|
||||
blue: "active:bg-blue/25",
|
||||
indigo: "active:bg-indigo-500/25",
|
||||
purple: "active:bg-purple/25",
|
||||
green: "active:bg-green/25",
|
||||
orange: "active:bg-orange/25",
|
||||
red: "active:bg-red/25",
|
||||
yellow: "active:bg-yellow/25",
|
||||
cyan: "active:bg-cyan/25",
|
||||
muted: "active:bg-stone-100/25 dark:active:bg-stone-900/25",
|
||||
strong: "active:bg-stone-900/25 dark:active:bg-stone-100/25",
|
||||
default: "active:bg-black/25 dark:active:bg-white/25",
|
||||
};
|
||||
|
||||
const gradiantClassesBase = "bg-gradient-to-t from-7% via-50% to-95%";
|
||||
|
||||
export const styleToBgGradientColorMap = {
|
||||
primary: `from-primary-dark via-primary to-primary-light ${gradiantClassesBase}`,
|
||||
info: `from-info-dark via-info to-info-light ${gradiantClassesBase}`,
|
||||
success: `from-success-dark via-success to-success-light ${gradiantClassesBase}`,
|
||||
warning: `from-warning-dark via-warning to-warning-light ${gradiantClassesBase}`,
|
||||
danger: `from-danger-dark via-danger to-danger-light ${gradiantClassesBase}`,
|
||||
alert: `from-alert-dark via-alert to-alert-light ${gradiantClassesBase}`,
|
||||
tip: `from-tip-dark via-tip to-tip-light ${gradiantClassesBase}`,
|
||||
muted: `from-stone-200 via-stone-300 to-stone-400 ${gradiantClassesBase} dark:from-stone-900 dark:via-stone-900 dark:to-stone-800`,
|
||||
strong: `from-stone-700 via-stone-800 to-stone-900 ${gradiantClassesBase} dark:from-stone-100 dark:via-stone-200 dark:to-stone-300`,
|
||||
default: `from-stone-200/40 via-white to-stone-100 ${gradiantClassesBase} dark:from-stone-900 dark:via-black dark:to-stone-950`,
|
||||
};
|
||||
|
||||
export const styleToBgGradientHoverMap = {
|
||||
primary: `hover:from-primary-brightLight hover:to-primary-light ${gradiantClassesBase}`,
|
||||
info: `hover:from-info-brightLight hover:to-info-light ${gradiantClassesBase}`,
|
||||
success: `hover:from-success-brightLight hover:to-success-light ${gradiantClassesBase}`,
|
||||
warning: `hover:from-warning-brightLight hover:to-warning-light ${gradiantClassesBase}`,
|
||||
danger: `hover:from-danger-brightLight hover:to-danger-light ${gradiantClassesBase}`,
|
||||
alert: `hover:from-alert-brightLight hover:to-alert-light ${gradiantClassesBase}`,
|
||||
tip: `hover:from-tip-brightLight hover:to-tip-light ${gradiantClassesBase}`,
|
||||
muted: `hover:from-stone-200 hover:to-stone-300 ${gradiantClassesBase} dark:hover:from-stone-900 dark:hover:to-stone-700/70`,
|
||||
strong: `hover:from-stone-700 hover:to-stone-800 ${gradiantClassesBase} dark:hover:from-stone-100 dark:hover:to-stone-200`,
|
||||
default: `hover:from-stone-100/50 hover:to-stone-100/50 dark:hover:from-stone-950 dark:hover:to-stone-900 ${gradiantClassesBase} border border-stone-100 dark:border-stone-900`,
|
||||
};
|
||||
|
||||
export const styleToBgGradientActiveMap = {
|
||||
primary: `active:from-primary-brightDark active:to-primary-light ${gradiantClassesBase}`,
|
||||
info: `active:from-info-brightDark active:to-info-light ${gradiantClassesBase}`,
|
||||
success: `active:from-success-brightDark active:to-success-light ${gradiantClassesBase}`,
|
||||
warning: `active:from-warning-brightDark active:to-warning-light ${gradiantClassesBase}`,
|
||||
danger: `active:from-danger-brightDark active:to-danger-light ${gradiantClassesBase}`,
|
||||
alert: `active:from-alert-brightDark active:to-alert-light ${gradiantClassesBase}`,
|
||||
tip: `active:from-tip-brightDark active:to-tip-light ${gradiantClassesBase}`,
|
||||
muted: `active:from-stone-300 active:to-stone-300 ${gradiantClassesBase} dark:active:from-stone-900 dark:active:to-stone-800`,
|
||||
strong: `active:from-stone-950 active:to-stone-900 ${gradiantClassesBase} dark:active:from-stone-100 dark:active:to-stone-200`,
|
||||
default: `active:from-stone-200/50 active:to-stone-100/50 dark:active:from-stone-950 dark:active:to-black ${gradiantClassesBase}`,
|
||||
};
|
||||
|
||||
export const shadowClassesBase = "shadow-sm";
|
||||
|
||||
export const styleToHoverShadowMap = {
|
||||
primary: `${shadowClassesBase} shadow-blue/20 hover:shadow-blue/40`,
|
||||
info: `${shadowClassesBase} shadow-purple/20 hover:shadow-purple/30`,
|
||||
success: `${shadowClassesBase} shadow-green/20 hover:shadow-green/30`,
|
||||
warning: `${shadowClassesBase} shadow-orange/20 hover:shadow-orange/30`,
|
||||
danger: `${shadowClassesBase} shadow-red/20 hover:shadow-red/30`,
|
||||
alert: `${shadowClassesBase} shadow-yellow/20 hover:shadow-yellow/30`,
|
||||
tip: `${shadowClassesBase} shadow-cyan/20 hover:shadow-cyan/30`,
|
||||
muted: `${shadowClassesBase} shadow-stone-200/20 hover:shadow-stone-200/30 dark:shadow-stone-600/20 dark:hover:shadow-stone-600/30`,
|
||||
strong: `${shadowClassesBase} shadow-stone-900/20 hover:shadow-stone-900/30 dark:shadow-white/20 dark:hover:shadow-white/30`,
|
||||
default: `${shadowClassesBase} shadow-stone-600/20 hover:shadow-stone-600/30 dark:shadow-stone-200/20 dark:hover:shadow-stone-200/30`,
|
||||
};
|
||||
|
||||
const focusRingClassesBase =
|
||||
"focus:outline-none focus-visible:ring focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-opacity-10";
|
||||
|
||||
export const styleToButtonStateMap = {
|
||||
primary: `${styleToBgGradientActiveMap.primary} ${focusRingClassesBase} focus:ring-primary`,
|
||||
info: `${styleToBgGradientActiveMap.info} ${focusRingClassesBase} focus:ring-info`,
|
||||
success: `${styleToBgGradientActiveMap.success} ${focusRingClassesBase} focus:ring-success`,
|
||||
warning: `${styleToBgGradientActiveMap.warning} ${focusRingClassesBase} focus:ring-warning`,
|
||||
danger: `${styleToBgGradientActiveMap.danger} ${focusRingClassesBase} focus:ring-danger`,
|
||||
alert: `${styleToBgGradientActiveMap.alert} ${focusRingClassesBase} focus:ring-alert`,
|
||||
tip: `${styleToBgGradientActiveMap.tip} ${focusRingClassesBase} focus:ring-tip`,
|
||||
muted: `${styleToBgGradientActiveMap.muted} ${focusRingClassesBase} focus:ring-stone-200 dark:focus:ring-stone-900`,
|
||||
strong: `${styleToBgGradientActiveMap.strong} ${focusRingClassesBase} focus:ring-stone-800 dark:focus:ring-stone-200`,
|
||||
default: `${styleToBgGradientActiveMap.default} ${focusRingClassesBase} focus:ring-black dark:focus:ring-white`,
|
||||
};
|
||||
|
||||
export const variantStyleToButtonStateMap = {
|
||||
outline: `${focusRingClassesBase}`,
|
||||
inverted: `${focusRingClassesBase}`,
|
||||
ghost: `${focusRingClassesBase}`,
|
||||
text: `${focusRingClassesBase}`,
|
||||
};
|
||||
@@ -30,6 +30,36 @@ const jazzBlue = {
|
||||
DEFAULT: COLORS.BLUE,
|
||||
};
|
||||
|
||||
const green = {
|
||||
...colors.green,
|
||||
DEFAULT: COLORS.FOREST,
|
||||
};
|
||||
|
||||
const cyan = {
|
||||
...colors.cyan,
|
||||
DEFAULT: COLORS.TURQUOISE,
|
||||
};
|
||||
|
||||
const red = {
|
||||
...colors.red,
|
||||
DEFAULT: COLORS.RED,
|
||||
};
|
||||
|
||||
const yellow = {
|
||||
...colors.yellow,
|
||||
DEFAULT: COLORS.YELLOW,
|
||||
};
|
||||
|
||||
const orange = {
|
||||
...colors.orange,
|
||||
DEFAULT: COLORS.ORANGE,
|
||||
};
|
||||
|
||||
const purple = {
|
||||
...colors.purple,
|
||||
DEFAULT: COLORS.PURPLE,
|
||||
};
|
||||
|
||||
const stonePaletteWithAlpha = { ...stonePalette };
|
||||
|
||||
Object.keys(stonePalette).forEach((key) => {
|
||||
@@ -40,11 +70,12 @@ Object.keys(stonePalette).forEach((key) => {
|
||||
});
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
const config = {
|
||||
export const preset = {
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/utils/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
@@ -52,28 +83,103 @@ const config = {
|
||||
...harmonyPalette,
|
||||
stone: stonePaletteWithAlpha,
|
||||
blue: jazzBlue,
|
||||
primary: "var(--color-primary)",
|
||||
secondary: "var(--color-secondary)",
|
||||
highlight: "var(--color-transparent-primary)",
|
||||
success: "var(--color-success)",
|
||||
info: "var(--color-info)",
|
||||
warning: "var(--color-warning)",
|
||||
tip: "var(--color-tip)",
|
||||
green: {
|
||||
DEFAULT: "var(--color-green)",
|
||||
green,
|
||||
cyan,
|
||||
red,
|
||||
yellow,
|
||||
purple,
|
||||
orange,
|
||||
muted: "var(--color-muted)",
|
||||
strong: "var(--color-strong)",
|
||||
primary: {
|
||||
DEFAULT: "var(--color-primary)",
|
||||
transparent: "var(--color-transparent-primary)",
|
||||
dark: "var(--color-primary-dark)",
|
||||
light:
|
||||
"lch(from var(--color-primary) calc(l + 10) calc(c + 1) calc(h - 5))",
|
||||
brightLight:
|
||||
"lch(from var(--color-primary) calc(l - 1) calc(c + 20) calc(h + 5))",
|
||||
brightDark:
|
||||
"lch(from var(--color-primary) calc(l - 6) calc(c + 20) calc(h + 5))",
|
||||
},
|
||||
success: {
|
||||
DEFAULT: "var(--color-success)",
|
||||
transparent: "lch(from var(--color-success) l c h / 0.3)",
|
||||
dark: "lch(from var(--color-success) calc(l - 7) calc(c - 1) calc(h + 5))",
|
||||
light:
|
||||
"lch(from var(--color-success) calc(l + 4) calc(c + 1) calc(h - 5))",
|
||||
brightLight:
|
||||
"lch(from var(--color-success) calc(l - 1) calc(c + 20) calc(h + 10))",
|
||||
brightDark:
|
||||
"lch(from var(--color-success) calc(l - 6) calc(c + 20) calc(h + 10))",
|
||||
},
|
||||
info: {
|
||||
DEFAULT: "var(--color-info)",
|
||||
transparent: "lch(from var(--color-info) l c h / 0.3)",
|
||||
dark: "lch(from var(--color-info) calc(l - 7) calc(c - 1) calc(h + 5))",
|
||||
light:
|
||||
"lch(from var(--color-info) calc(l + 4) calc(c + 1) calc(h - 5))",
|
||||
brightLight:
|
||||
"lch(from var(--color-info) calc(l - 1) calc(c + 20) calc(h + 5))",
|
||||
brightDark:
|
||||
"lch(from var(--color-info) calc(l - 4) calc(c + 20) calc(h + 5))",
|
||||
},
|
||||
warning: {
|
||||
DEFAULT: "var(--color-warning)",
|
||||
transparent: "lch(from var(--color-warning) l c h / 0.3)",
|
||||
dark: "lch(from var(--color-warning) calc(l - 7) calc(c - 1) calc(h + 5))",
|
||||
light:
|
||||
"lch(from var(--color-warning) calc(l + 4) calc(c + 1) calc(h - 5))",
|
||||
brightLight:
|
||||
"lch(from var(--color-warning) calc(l - 1) calc(c + 30) calc(h + 15))",
|
||||
brightDark:
|
||||
"lch(from var(--color-warning) calc(l - 4) calc(c + 30) calc(h + 15))",
|
||||
},
|
||||
danger: {
|
||||
DEFAULT: "var(--color-danger)",
|
||||
transparent: "lch(from var(--color-danger) l c h / 0.3)",
|
||||
dark: "lch(from var(--color-danger) calc(l - 7) calc(c - 1) calc(h + 5))",
|
||||
light:
|
||||
"lch(from var(--color-danger) calc(l + 4) calc(c + 1) calc(h - 5))",
|
||||
brightLight:
|
||||
"lch(from var(--color-danger) calc(l - 2) calc(c + 20) calc(h + 10))",
|
||||
brightDark:
|
||||
"lch(from var(--color-danger) calc(l - 6) calc(c + 10) calc(h + 10))",
|
||||
},
|
||||
tip: {
|
||||
DEFAULT: "var(--color-tip)",
|
||||
transparent: "lch(from var(--color-tip) l c h / 0.3)",
|
||||
dark: "lch(from var(--color-tip) calc(l - 7) calc(c - 1) calc(h + 5))",
|
||||
light:
|
||||
"lch(from var(--color-tip) calc(l + 4) calc(c + 1) calc(h - 5))",
|
||||
brightLight:
|
||||
"lch(from var(--color-tip) calc(l - 1) calc(c + 20) calc(h + 10))",
|
||||
brightDark:
|
||||
"lch(from var(--color-tip) calc(l - 4) calc(c + 20) calc(h + 10))",
|
||||
},
|
||||
alert: {
|
||||
DEFAULT: "var(--color-alert)",
|
||||
transparent: "lch(from var(--color-alert) l c h / 0.3)",
|
||||
dark: "lch(from var(--color-alert) calc(l - 7) calc(c - 1) calc(h + 5))",
|
||||
light:
|
||||
"lch(from var(--color-alert) calc(l + 4) calc(c + 1) calc(h - 5))",
|
||||
brightLight:
|
||||
"lch(from var(--color-alert) calc(l - 1) calc(c + 50) calc(h + 15))",
|
||||
brightDark:
|
||||
"lch(from var(--color-alert) calc(l - 5) calc(c + 50) calc(h + 15))",
|
||||
},
|
||||
},
|
||||
textColor: {
|
||||
default: "var(--color-default)",
|
||||
highlight: "var(--color-highlight)",
|
||||
strong: "var(--color-strong)",
|
||||
muted: "var(--color-muted)",
|
||||
},
|
||||
borderColor: {
|
||||
DEFAULT: "var(--color-border-default)",
|
||||
},
|
||||
backgroundColor: {
|
||||
highlight: "var(--color-transparent-primary)",
|
||||
muted: "var(--color-bg-muted)",
|
||||
highlight: "var(--color-background-highlight)",
|
||||
},
|
||||
fontFamily: {
|
||||
display: ["var(--font-manrope)"],
|
||||
@@ -195,4 +301,10 @@ const config = {
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
presets: [preset],
|
||||
darkMode: ["class"],
|
||||
content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
|
||||
};
|
||||
export default config;
|
||||
|
||||
@@ -4,7 +4,12 @@ import { GcmpLogo } from "@garden-co/design-system/src/components/atoms/logos/Gc
|
||||
import { Nav } from "@garden-co/design-system/src/components/organisms/Nav";
|
||||
export function GcmpNav() {
|
||||
const cta = (
|
||||
<Button variant="secondary" className="ml-3" href="mailto:hello@garden.co">
|
||||
<Button
|
||||
intent="success"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
href="mailto:hello@garden.co"
|
||||
>
|
||||
Contact us
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -31,7 +31,11 @@ export default function Products() {
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<JazzLogo className="h-10 w-auto" />
|
||||
<div>
|
||||
<Button href="https://jazz.tools" variant="secondary">
|
||||
<Button
|
||||
href="https://jazz.tools"
|
||||
intent="primary"
|
||||
variant="outline"
|
||||
>
|
||||
Go to jazz.tools
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
const config: Config = {
|
||||
presets: [require("@garden-co/design-system/tailwind.config.js")],
|
||||
presets: [require("@garden-co/design-system/tailwind.config.js").preset],
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
|
||||
@@ -13,7 +13,8 @@ export function FakeGetStartedButton({ tier }: { tier: "starter" | "indie" }) {
|
||||
);
|
||||
window.location.pathname = "/docs";
|
||||
}}
|
||||
variant={tier === "starter" ? "secondary" : "primary"}
|
||||
intent="primary"
|
||||
variant={tier === "starter" ? "outline" : undefined}
|
||||
>
|
||||
Get {tier === "starter" ? "Starter" : "Indie"} API Key
|
||||
</Button>
|
||||
|
||||
@@ -180,7 +180,7 @@ export function Pricing() {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<Button href="https://cal.com/anselm-io/cloud-pro-intro">
|
||||
<Button href="https://cal.com/anselm-io/cloud-pro-intro" intent="primary">
|
||||
Schedule intro call
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -37,7 +37,10 @@ export function SideNavSectionList({ items }: { items?: SideNavItem[] }) {
|
||||
{items.map(({ name, href, items, done }) => (
|
||||
<li key={name}>
|
||||
<SideNavItem href={href}>
|
||||
<span className={done === 0 ? "text-muted" : ""}>{name}</span>
|
||||
<span className={done === 0 ? "text-muted" : ""}>
|
||||
{name}
|
||||
{done === 0 && <span className="text-stone-800 text-[0.5rem]"> (docs coming soon)</span>}
|
||||
</span>
|
||||
</SideNavItem>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -31,10 +31,11 @@ export function FrameworkSelect() {
|
||||
<DropdownButton
|
||||
className="w-full justify-between"
|
||||
as={Button}
|
||||
variant="secondary"
|
||||
variant="outline"
|
||||
intent="default"
|
||||
>
|
||||
{frameworkNames[selectedFramework].label}
|
||||
<Icon name="chevronDown" size="sm" className="text-muted" />
|
||||
<Icon name="chevronDown" size="sm" />
|
||||
</DropdownButton>
|
||||
<DropdownMenu className="w-[--button-width] z-50" anchor="bottom start">
|
||||
{Object.entries(frameworkNames)
|
||||
|
||||
@@ -19,9 +19,6 @@ export function HelpLinks({ className }: { className?: string }) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const linkClassName =
|
||||
"inline-flex items-center gap-2 py-1 text-sm text-stone-600 dark:text-stone-400 hover:text-highlight";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
@@ -29,15 +26,16 @@ export function HelpLinks({ className }: { className?: string }) {
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Button href={issueUrl} variant="plain" newTab className={linkClassName}>
|
||||
<Button href={issueUrl} intent="strong" variant="ghost" newTab>
|
||||
<SiGithub className="size-4" />
|
||||
Docs issue?
|
||||
</Button>
|
||||
<Button
|
||||
href="https://discord.gg/utDMjHYg42"
|
||||
variant="plain"
|
||||
intent="strong"
|
||||
variant="ghost"
|
||||
newTab
|
||||
className={linkClassName}
|
||||
className="lg:mt-3"
|
||||
>
|
||||
<SiDiscord className="size-4" />
|
||||
Join Discord
|
||||
|
||||
@@ -25,16 +25,16 @@ export function ExampleLinks({ example }: { example: Example }) {
|
||||
return (
|
||||
<>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" size="sm" onClick={() => setIsOpen(true)}>
|
||||
<Button intent="primary" variant="inverted" size="sm" onClick={() => setIsOpen(true)}>
|
||||
Use as template
|
||||
</Button>
|
||||
<Button href={githubUrl} newTab variant="secondary" size="sm">
|
||||
<Button href={githubUrl} newTab intent="primary" variant="inverted" size="sm">
|
||||
<span className="md:hidden">Code</span>
|
||||
<span className="hidden md:inline">View code</span>
|
||||
</Button>
|
||||
|
||||
{demoUrl && (
|
||||
<Button href={demoUrl} newTab variant="secondary" size="sm">
|
||||
<Button href={demoUrl} newTab intent="primary" variant="inverted" size="sm">
|
||||
<span className="md:hidden">Demo</span>
|
||||
<span className="hidden md:inline">View demo</span>
|
||||
</Button>
|
||||
|
||||
@@ -6,7 +6,10 @@ import { Testimonial } from "@garden-co/design-system/src/components/molecules/T
|
||||
function TheoTestimonial({
|
||||
size,
|
||||
className,
|
||||
}: { size?: "sm" | "md"; className?: string }) {
|
||||
}: {
|
||||
size?: "sm" | "md";
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<Testimonial
|
||||
size={size}
|
||||
@@ -31,7 +34,7 @@ function TheoTestimonial({
|
||||
|
||||
export function EarlyAdopterSection() {
|
||||
return (
|
||||
<div className="grid grid grid-cols-3 items-center gap-y-12">
|
||||
<div className="grid grid-cols-3 items-center gap-y-12">
|
||||
<TheoTestimonial size="md" className="col-span-3 lg:hidden" />
|
||||
<div className="col-span-3 lg:col-span-2">
|
||||
<div className="max-w-3xl space-y-6">
|
||||
@@ -53,10 +56,14 @@ export function EarlyAdopterSection() {
|
||||
</Prose>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Button href="/docs" variant="primary">
|
||||
<Button href="/docs" intent="primary">
|
||||
Read docs
|
||||
</Button>
|
||||
<Button href="https://discord.gg/utDMjHYg42" variant="secondary">
|
||||
<Button
|
||||
href="https://discord.gg/utDMjHYg42"
|
||||
intent="primary"
|
||||
variant="outline"
|
||||
>
|
||||
Join Discord
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -51,7 +51,9 @@ function Illustration() {
|
||||
<Icon
|
||||
name="encryption"
|
||||
size="3xl"
|
||||
className="z-30 text-primary p-1.5 rounded-lg bg-highlight dark:bg-stone-900"
|
||||
className="z-30 p-1.5 rounded-lg"
|
||||
intent="primary"
|
||||
hasBackground
|
||||
/>
|
||||
|
||||
{/*<LockKeyholeIcon*/}
|
||||
@@ -72,7 +74,7 @@ function Illustration() {
|
||||
export function EncryptionSection() {
|
||||
return (
|
||||
<Card className="overflow-hidden dark:bg-stone-925">
|
||||
<div className="flex grid md:grid-cols-3 md:gap-3">
|
||||
<div className="grid md:grid-cols-3 md:gap-3">
|
||||
<div className="md:col-span-2 px-4 pb-4 md:p-8">
|
||||
<H3 className="mb-0 text-balance">
|
||||
End-to-end encrypted and tamper-proof
|
||||
|
||||
@@ -139,7 +139,7 @@ export function FeaturesSection() {
|
||||
</p>
|
||||
</Prose>
|
||||
<div className="flex items-center flex-wrap gap-3">
|
||||
<Button href="/cloud" variant="primary">
|
||||
<Button href="/cloud" intent="primary">
|
||||
View free tier & pricing
|
||||
</Button>
|
||||
|
||||
@@ -161,8 +161,8 @@ export function FeaturesSection() {
|
||||
key={feature}
|
||||
className="flex items-center gap-1.5 whitespace-nowrap"
|
||||
>
|
||||
<span className="text-primary p-1 rounded-full bg-highlight">
|
||||
<Icon name="check" size="xs" />
|
||||
<span className="p-1 rounded-full bg-primary-transparent">
|
||||
<Icon name="check" size="xs" intent="primary" />
|
||||
</span>
|
||||
{feature}
|
||||
</li>
|
||||
|
||||
@@ -86,8 +86,8 @@ export function HeroSection() {
|
||||
key={title}
|
||||
className="flex text-xs sm:text-sm gap-2 items-center"
|
||||
>
|
||||
<span className="text-primary p-1.5 rounded-lg bg-highlight dark:bg-stone-900">
|
||||
<Icon size="xs" name={icon} />
|
||||
<span className="p-1.5 rounded-lg bg-primary-transparent">
|
||||
<Icon size="xs" name={icon} intent="primary" />
|
||||
</span>
|
||||
<p>{title}</p>
|
||||
</div>
|
||||
|
||||
@@ -63,9 +63,8 @@ function Step({
|
||||
<div className="flex gap-3 px-4 sm:px-6">
|
||||
<p
|
||||
className={clsx(
|
||||
"bg-highlight size-6 rounded-full text-primary text-sm font-semibold font-mono",
|
||||
"bg-primary-transparent text-primary size-6 rounded-full text-sm font-semibold font-mono",
|
||||
"inline-flex items-center justify-center text-center shrink-0",
|
||||
"dark:bg-blue dark:text-white",
|
||||
)}
|
||||
>
|
||||
<span className="sr-only">Step</span>
|
||||
|
||||
@@ -15,15 +15,16 @@ export function QuickSearch() {
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="group xl:min-w-48 text-stone-600 md:mr-5"
|
||||
variant="secondary"
|
||||
className="group xl:min-w-48 md:mr-5"
|
||||
intent="muted"
|
||||
variant="outline"
|
||||
onClick={() => setOpen((open) => !open)}
|
||||
>
|
||||
<Icon name="search" size="xs" className=" text-stone-600" />
|
||||
<span className="font-normal flex-1 text-left text-sm text-stone-600 group-hover:text-blue xl:not-sr-only">
|
||||
<Icon name="search" size="xs" intent="default" />
|
||||
<span className="font-normal flex-1 text-left text-sm xl:not-sr-only">
|
||||
Search docs
|
||||
</span>
|
||||
<kbd className="hidden gap-0.5 xl:text-sm text-stone-600 lg:inline-flex">
|
||||
<kbd className="hidden gap-0.5 xl:text-sm lg:inline-flex">
|
||||
<kbd className="font-sans">{isMac ? "⌘" : "Ctrl"}</kbd>
|
||||
<kbd className="font-sans">K</kbd>
|
||||
</kbd>
|
||||
|
||||
@@ -118,7 +118,7 @@ To use it, install the following Packages:
|
||||
|
||||
<CodeGroup>
|
||||
```bash
|
||||
pnpm add react-native-quick-crypto@1.0.0-beta.18 react-native-nitro-modules
|
||||
pnpm add react-native-quick-crypto@1.0.0-beta.18 react-native-nitro-modules react-native-fast-encoder
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ To use it, install the following Packages:
|
||||
|
||||
<CodeGroup>
|
||||
```bash
|
||||
pnpm add react-native-quick-crypto@1.0.0-beta.18 react-native-nitro-modules
|
||||
pnpm add react-native-quick-crypto@1.0.0-beta.18 react-native-nitro-modules react-native-fast-encoder
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ npx expo prebuild
|
||||
<CodeGroup>
|
||||
```bash
|
||||
# Expo dependencies
|
||||
npx expo install expo-linking expo-secure-store expo-file-system @react-native-community/netinfo @bam.tech/react-native-image-resizer
|
||||
npx expo install expo-linking expo-secure-store expo-sqlite expo-file-system @react-native-community/netinfo @bam.tech/react-native-image-resizer
|
||||
|
||||
# React Native polyfills
|
||||
npm i -S @azure/core-asynciterator-polyfill react-native-url-polyfill readable-stream react-native-get-random-values
|
||||
|
||||
@@ -7,7 +7,7 @@ export const colourSafelist = Object.values(pingColorMap).flatMap((value) => {
|
||||
});
|
||||
|
||||
const config: Config = {
|
||||
presets: [require("@garden-co/design-system/tailwind.config.js")],
|
||||
presets: [require("@garden-co/design-system/tailwind.config.js").preset],
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
|
||||
26
homepage/pnpm-lock.yaml
generated
26
homepage/pnpm-lock.yaml
generated
@@ -65,6 +65,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: 'catalog:'
|
||||
version: 19.0.0(react@19.0.0)
|
||||
react-hot-toast:
|
||||
specifier: ^2.5.2
|
||||
version: 2.5.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
resend:
|
||||
specifier: ^4.0.0
|
||||
version: 4.5.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
@@ -3415,6 +3418,11 @@ packages:
|
||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
goober@2.1.16:
|
||||
resolution: {integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==}
|
||||
peerDependencies:
|
||||
csstype: ^3.0.10
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
@@ -4421,6 +4429,13 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^19.0.0
|
||||
|
||||
react-hot-toast@2.5.2:
|
||||
resolution: {integrity: sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
react: '>=16'
|
||||
react-dom: '>=16'
|
||||
|
||||
react-is@18.3.1:
|
||||
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
||||
|
||||
@@ -9166,6 +9181,10 @@ snapshots:
|
||||
|
||||
globals@11.12.0: {}
|
||||
|
||||
goober@2.1.16(csstype@3.1.3):
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
@@ -10723,6 +10742,13 @@ snapshots:
|
||||
react: 19.0.0
|
||||
scheduler: 0.25.0
|
||||
|
||||
react-hot-toast@2.5.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
goober: 2.1.16(csstype@3.1.3)
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
react-is@18.3.1: {}
|
||||
|
||||
react-native@0.79.3(@babel/core@7.27.4)(@types/react@19.0.0)(react@19.0.0):
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
"playwright": "^1.50.1",
|
||||
"turbo": "^2.3.1",
|
||||
"typedoc": "^0.25.13",
|
||||
"vitest": "catalog:",
|
||||
"yalc": "^1.0.0-pre.53"
|
||||
"vitest": "catalog:"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "turbo dev",
|
||||
"build": "turbo build && cd homepage/homepage && turbo build",
|
||||
"build:packages": "turbo build --filter='./packages/*'",
|
||||
"lint": "turbo lint && cd homepage/homepage && pnpm run lint",
|
||||
"test": "vitest",
|
||||
"test:ci": "vitest run --watch=false --coverage.enabled=true",
|
||||
@@ -39,8 +39,7 @@
|
||||
"changeset-version": "changeset version && pnpm i --no-frozen-lockfile",
|
||||
"release": "turbo run build --filter='./packages/*' && pnpm changeset publish && git push --follow-tags",
|
||||
"clean": "rm -rf ./packages/*/dist && rm -rf ./packages/*/node_modules && rm -rf ./examples/*/node_modules && rm -rf ./examples/*/dist",
|
||||
"check-catalog-deps": "node scripts/check-catalog-deps.js",
|
||||
"yalc:all": "for d in packages/*/; do echo $d; cd $d; pnpm yalc push --replace --sig; cd '../../'; done"
|
||||
"check-catalog-deps": "node scripts/check-catalog-deps.js"
|
||||
},
|
||||
"version": "0.0.0",
|
||||
"pnpm": {
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# cojson-storage-indexeddb
|
||||
|
||||
## 0.15.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.15.6
|
||||
- cojson-storage@0.15.6
|
||||
|
||||
## 0.15.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.15.5
|
||||
- cojson-storage@0.15.5
|
||||
|
||||
## 0.15.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [277e4d4]
|
||||
- cojson@0.15.4
|
||||
- cojson-storage@0.15.4
|
||||
|
||||
## 0.15.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 535c460: Refresh the IndexedDB transaction when getting "Failed to execute 'objectStore' on 'IDBTransaction': The transaction has finished"
|
||||
- cojson@0.15.3
|
||||
- cojson-storage@0.15.3
|
||||
|
||||
## 0.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cojson-storage-indexeddb",
|
||||
"version": "0.15.2",
|
||||
"version": "0.15.6",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -9,7 +9,7 @@ export type StoreName =
|
||||
// in a single transaction.
|
||||
export class CoJsonIDBTransaction {
|
||||
db: IDBDatabase;
|
||||
tx: IDBTransaction;
|
||||
declare tx: IDBTransaction;
|
||||
|
||||
pendingRequests: ((txEntry: this) => void)[] = [];
|
||||
rejectHandlers: (() => void)[] = [];
|
||||
@@ -23,6 +23,10 @@ export class CoJsonIDBTransaction {
|
||||
constructor(db: IDBDatabase) {
|
||||
this.db = db;
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.tx = this.db.transaction(
|
||||
["coValues", "sessions", "transactions", "signatureAfter"],
|
||||
"readwrite",
|
||||
@@ -43,7 +47,12 @@ export class CoJsonIDBTransaction {
|
||||
}
|
||||
|
||||
getObjectStore(name: StoreName) {
|
||||
return this.tx.objectStore(name);
|
||||
try {
|
||||
return this.tx.objectStore(name);
|
||||
} catch (error) {
|
||||
this.refresh();
|
||||
return this.tx.objectStore(name);
|
||||
}
|
||||
}
|
||||
|
||||
private pushRequest<T>(
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# cojson-storage-sqlite
|
||||
|
||||
## 0.15.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.15.6
|
||||
- cojson-storage@0.15.6
|
||||
|
||||
## 0.15.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.15.5
|
||||
- cojson-storage@0.15.5
|
||||
|
||||
## 0.15.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [277e4d4]
|
||||
- cojson@0.15.4
|
||||
- cojson-storage@0.15.4
|
||||
|
||||
## 0.15.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.15.3
|
||||
- cojson-storage@0.15.3
|
||||
|
||||
## 0.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "cojson-storage-sqlite",
|
||||
"type": "module",
|
||||
"version": "0.15.2",
|
||||
"version": "0.15.6",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^11.7.0",
|
||||
"cojson": "workspace:0.15.2",
|
||||
"cojson": "workspace:0.15.6",
|
||||
"cojson-storage": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# cojson-storage
|
||||
|
||||
## 0.15.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.15.6
|
||||
|
||||
## 0.15.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.15.5
|
||||
|
||||
## 0.15.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [277e4d4]
|
||||
- cojson@0.15.4
|
||||
|
||||
## 0.15.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.15.3
|
||||
|
||||
## 0.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cojson-storage",
|
||||
"version": "0.15.2",
|
||||
"version": "0.15.6",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# cojson-transport-nodejs-ws
|
||||
|
||||
## 0.15.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.15.6
|
||||
|
||||
## 0.15.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.15.5
|
||||
|
||||
## 0.15.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [277e4d4]
|
||||
- cojson@0.15.4
|
||||
|
||||
## 0.15.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.15.3
|
||||
|
||||
## 0.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cojson-transport-ws",
|
||||
"type": "module",
|
||||
"version": "0.15.2",
|
||||
"version": "0.15.6",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# cojson
|
||||
|
||||
## 0.15.6
|
||||
|
||||
## 0.15.5
|
||||
|
||||
## 0.15.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 277e4d4: Fix sync with circular deps
|
||||
|
||||
## 0.15.3
|
||||
|
||||
## 0.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "0.15.2",
|
||||
"version": "0.15.6",
|
||||
"devDependencies": {
|
||||
"@opentelemetry/sdk-metrics": "^2.0.0",
|
||||
"typescript": "catalog:"
|
||||
|
||||
@@ -207,7 +207,17 @@ export class SyncManager {
|
||||
}
|
||||
}
|
||||
|
||||
sendNewContentIncludingDependencies(id: RawCoID, peer: PeerState) {
|
||||
sendNewContentIncludingDependencies(
|
||||
id: RawCoID,
|
||||
peer: PeerState,
|
||||
seen: Set<RawCoID> = new Set(),
|
||||
) {
|
||||
if (seen.has(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
seen.add(id);
|
||||
|
||||
const coValue = this.local.getCoValue(id);
|
||||
|
||||
if (!coValue.isAvailable()) {
|
||||
@@ -215,7 +225,7 @@ export class SyncManager {
|
||||
}
|
||||
|
||||
for (const dependency of coValue.getDependedOnCoValues()) {
|
||||
this.sendNewContentIncludingDependencies(dependency, peer);
|
||||
this.sendNewContentIncludingDependencies(dependency, peer, seen);
|
||||
}
|
||||
|
||||
const newContentPieces = coValue.verified.newContentSince(
|
||||
@@ -494,7 +504,7 @@ export class SyncManager {
|
||||
)) {
|
||||
const dependencyCoValue = this.local.getCoValue(dependency);
|
||||
|
||||
if (!dependencyCoValue.isAvailable()) {
|
||||
if (!dependencyCoValue.verified) {
|
||||
coValue.markMissingDependency(dependency);
|
||||
|
||||
if (!dependencyCoValue.verified) {
|
||||
|
||||
@@ -826,4 +826,46 @@ describe("loading coValues from server", () => {
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test("coValue with circular deps loading", async () => {
|
||||
const client = setupTestNode({
|
||||
connected: true,
|
||||
});
|
||||
|
||||
const group = client.node.createGroup();
|
||||
const parentGroup = client.node.createGroup();
|
||||
parentGroup.addMember("everyone", "reader");
|
||||
|
||||
group.extend(parentGroup);
|
||||
|
||||
// Disable the circular dependency check in the extend function
|
||||
vi.spyOn(parentGroup, "isSelfExtension").mockImplementation(() => false);
|
||||
|
||||
parentGroup.extend(group);
|
||||
|
||||
const map = group.createMap();
|
||||
map.set("hello", "world");
|
||||
|
||||
await map.core.waitForSync();
|
||||
|
||||
expect(
|
||||
SyncMessagesLog.getMessages({
|
||||
ParentGroup: parentGroup.core,
|
||||
Group: group.core,
|
||||
Map: map.core,
|
||||
}),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> server | CONTENT ParentGroup header: true new: After: 0 New: 8",
|
||||
"server -> client | LOAD Group sessions: empty",
|
||||
"client -> server | CONTENT Group header: true new: After: 0 New: 6",
|
||||
"server -> client | KNOWN ParentGroup sessions: empty",
|
||||
"client -> server | CONTENT Group header: true new: After: 0 New: 6",
|
||||
"server -> client | KNOWN Group sessions: header/6",
|
||||
"client -> server | CONTENT Map header: true new: After: 0 New: 1",
|
||||
"server -> client | KNOWN Group sessions: header/6",
|
||||
"server -> client | KNOWN Map sessions: header/1",
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# create-jazz-app
|
||||
|
||||
## 0.1.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b774bb3: Add default project name
|
||||
|
||||
## 0.1.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"types": "src/index.ts",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "0.1.24",
|
||||
"version": "0.1.25",
|
||||
"bin": {
|
||||
"create-jazz-app": "./dist/index.js"
|
||||
},
|
||||
|
||||
@@ -493,10 +493,24 @@ async function promptUser(
|
||||
}
|
||||
|
||||
if (!partialOptions.projectName) {
|
||||
// Determine a default project name if possible
|
||||
let defaultProjectName = undefined;
|
||||
if (partialOptions.example) {
|
||||
// Use the example name, affixed with -app if not already
|
||||
defaultProjectName = partialOptions.example.endsWith("-app")
|
||||
? partialOptions.example
|
||||
: `${partialOptions.example}-app`;
|
||||
} else if (partialOptions.starter) {
|
||||
// Use the starter name, affixed with -app if not already
|
||||
defaultProjectName = partialOptions.starter.endsWith("-app")
|
||||
? partialOptions.starter
|
||||
: `${partialOptions.starter}-app`;
|
||||
}
|
||||
questions.push({
|
||||
type: "input",
|
||||
name: "projectName",
|
||||
message: chalk.cyan("Enter your project name:"),
|
||||
default: defaultProjectName,
|
||||
validate: (input: string) =>
|
||||
input ? true : chalk.red("Project name cannot be empty"),
|
||||
});
|
||||
@@ -590,6 +604,17 @@ program
|
||||
} else if (directory && !options.projectName) {
|
||||
// If directory is provided but not project name, use directory as project name
|
||||
partialOptions.projectName = directory;
|
||||
} else if (!directory && !options.projectName) {
|
||||
// If no directory or projectName, and example is provided, use example as default
|
||||
if (options.example) {
|
||||
partialOptions.projectName = options.example.endsWith("-app")
|
||||
? options.example
|
||||
: `${options.example}-app`;
|
||||
} else if (options.starter) {
|
||||
partialOptions.projectName = options.starter.endsWith("-app")
|
||||
? options.starter
|
||||
: `${options.starter}-app`;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.starter)
|
||||
|
||||
@@ -1,5 +1,44 @@
|
||||
# jazz-auth-betterauth
|
||||
|
||||
## 0.15.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a5ceaff]
|
||||
- jazz-tools@0.15.6
|
||||
- jazz-betterauth-client-plugin@0.15.6
|
||||
- cojson@0.15.6
|
||||
|
||||
## 0.15.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4b89838: Changes that verify the BetterAuth package for React also works with React Native and Expo apps
|
||||
- Updated dependencies [23bfea5]
|
||||
- Updated dependencies [e4ba23c]
|
||||
- Updated dependencies [4b89838]
|
||||
- jazz-tools@0.15.5
|
||||
- jazz-betterauth-client-plugin@0.15.5
|
||||
- cojson@0.15.5
|
||||
|
||||
## 0.15.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [277e4d4]
|
||||
- cojson@0.15.4
|
||||
- jazz-tools@0.15.4
|
||||
- jazz-betterauth-client-plugin@0.15.4
|
||||
|
||||
## 0.15.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [45f73a7]
|
||||
- jazz-tools@0.15.3
|
||||
- jazz-betterauth-client-plugin@0.15.3
|
||||
- cojson@0.15.3
|
||||
|
||||
## 0.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-auth-betterauth",
|
||||
"version": "0.15.2",
|
||||
"version": "0.15.6",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -9,29 +9,33 @@ import {
|
||||
} from "jazz-tools";
|
||||
import type { AuthSetPayload } from "jazz-tools";
|
||||
|
||||
export const newAuthClient = <T extends ClientOptions>(options?: T) => {
|
||||
type Plugins = Array<
|
||||
| NonNullable<NonNullable<T>["plugins"]>[number]
|
||||
| ReturnType<typeof jazzClientPlugin>
|
||||
type Plugins<T extends ClientOptions> = Array<
|
||||
| NonNullable<NonNullable<T>["plugins"]>[number]
|
||||
| ReturnType<typeof jazzClientPlugin>
|
||||
>;
|
||||
|
||||
type Options<T extends ClientOptions> = {
|
||||
plugins: Plugins<T>;
|
||||
} & T;
|
||||
|
||||
export type AuthClient<T extends ClientOptions> = ReturnType<
|
||||
typeof createAuthClient<Options<T>>
|
||||
> &
|
||||
ReturnType<
|
||||
typeof createAuthClient<{
|
||||
plugins: [ReturnType<typeof jazzClientPlugin>];
|
||||
}>
|
||||
>;
|
||||
type Options<T> = {
|
||||
plugins: Plugins;
|
||||
} & T;
|
||||
type AuthClient<T> = ReturnType<typeof createAuthClient<Options<T>>> &
|
||||
ReturnType<
|
||||
typeof createAuthClient<{
|
||||
plugins: [ReturnType<typeof jazzClientPlugin>];
|
||||
}>
|
||||
>;
|
||||
|
||||
export const newAuthClient = <T extends ClientOptions>(
|
||||
options?: T,
|
||||
): AuthClient<T> => {
|
||||
return createAuthClient<Options<T>>({
|
||||
...options,
|
||||
plugins: [...(options?.plugins ?? []), ...[jazzClientPlugin()]],
|
||||
} as Options<T>) as AuthClient<T>;
|
||||
};
|
||||
|
||||
export type AuthClient<T extends ClientOptions> = ReturnType<
|
||||
typeof newAuthClient<T>
|
||||
>;
|
||||
export type InferredSession<T extends ClientOptions> =
|
||||
AuthClient<T>["$Infer"]["Session"];
|
||||
export type Session<T extends ClientOptions> =
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# jazz-betterauth-client-plugin
|
||||
|
||||
## 0.15.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-betterauth-server-plugin@0.15.6
|
||||
|
||||
## 0.15.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-betterauth-server-plugin@0.15.5
|
||||
|
||||
## 0.15.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-betterauth-server-plugin@0.15.4
|
||||
|
||||
## 0.15.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-betterauth-server-plugin@0.15.3
|
||||
|
||||
## 0.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-betterauth-client-plugin",
|
||||
"version": "0.15.2",
|
||||
"version": "0.15.6",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# jazz-betterauth-server-plugin
|
||||
|
||||
## 0.15.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a5ceaff]
|
||||
- jazz-tools@0.15.6
|
||||
- cojson@0.15.6
|
||||
|
||||
## 0.15.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [23bfea5]
|
||||
- Updated dependencies [e4ba23c]
|
||||
- Updated dependencies [4b89838]
|
||||
- jazz-tools@0.15.5
|
||||
- cojson@0.15.5
|
||||
|
||||
## 0.15.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [277e4d4]
|
||||
- cojson@0.15.4
|
||||
- jazz-tools@0.15.4
|
||||
|
||||
## 0.15.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [45f73a7]
|
||||
- jazz-tools@0.15.3
|
||||
- cojson@0.15.3
|
||||
|
||||
## 0.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-betterauth-server-plugin",
|
||||
"version": "0.15.2",
|
||||
"version": "0.15.6",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,5 +1,48 @@
|
||||
# jazz-react-auth-betterauth
|
||||
|
||||
## 0.15.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a5ceaff]
|
||||
- jazz-tools@0.15.6
|
||||
- jazz-auth-betterauth@0.15.6
|
||||
- jazz-betterauth-client-plugin@0.15.6
|
||||
- cojson@0.15.6
|
||||
|
||||
## 0.15.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4b89838: Changes that verify the BetterAuth package for React also works with React Native and Expo apps
|
||||
- Updated dependencies [23bfea5]
|
||||
- Updated dependencies [e4ba23c]
|
||||
- Updated dependencies [4b89838]
|
||||
- jazz-tools@0.15.5
|
||||
- jazz-auth-betterauth@0.15.5
|
||||
- jazz-betterauth-client-plugin@0.15.5
|
||||
- cojson@0.15.5
|
||||
|
||||
## 0.15.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [277e4d4]
|
||||
- cojson@0.15.4
|
||||
- jazz-auth-betterauth@0.15.4
|
||||
- jazz-tools@0.15.4
|
||||
- jazz-betterauth-client-plugin@0.15.4
|
||||
|
||||
## 0.15.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [45f73a7]
|
||||
- jazz-tools@0.15.3
|
||||
- jazz-auth-betterauth@0.15.3
|
||||
- jazz-betterauth-client-plugin@0.15.3
|
||||
- cojson@0.15.3
|
||||
|
||||
## 0.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-react-auth-betterauth",
|
||||
"version": "0.15.2",
|
||||
"version": "0.15.6",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.tsx",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type { ClientOptions } from "better-auth/client";
|
||||
import { createContext, useContext } from "react";
|
||||
import { useBetterAuth } from "../index.js";
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
useAuthSecretStorage,
|
||||
useIsAuthenticated,
|
||||
useJazzContext,
|
||||
} from "jazz-tools/react";
|
||||
} from "jazz-tools/react-core";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
export * from "./contexts/Auth.js";
|
||||
|
||||
@@ -1,5 +1,47 @@
|
||||
# jazz-run
|
||||
|
||||
## 0.15.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a5ceaff]
|
||||
- jazz-tools@0.15.6
|
||||
- cojson@0.15.6
|
||||
- cojson-storage-sqlite@0.15.6
|
||||
- cojson-transport-ws@0.15.6
|
||||
|
||||
## 0.15.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [23bfea5]
|
||||
- Updated dependencies [e4ba23c]
|
||||
- Updated dependencies [4b89838]
|
||||
- jazz-tools@0.15.5
|
||||
- cojson@0.15.5
|
||||
- cojson-storage-sqlite@0.15.5
|
||||
- cojson-transport-ws@0.15.5
|
||||
|
||||
## 0.15.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [277e4d4]
|
||||
- cojson@0.15.4
|
||||
- cojson-storage-sqlite@0.15.4
|
||||
- cojson-transport-ws@0.15.4
|
||||
- jazz-tools@0.15.4
|
||||
|
||||
## 0.15.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [45f73a7]
|
||||
- jazz-tools@0.15.3
|
||||
- cojson@0.15.3
|
||||
- cojson-storage-sqlite@0.15.3
|
||||
- cojson-transport-ws@0.15.3
|
||||
|
||||
## 0.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"bin": "./dist/index.js",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "0.15.2",
|
||||
"version": "0.15.6",
|
||||
"exports": {
|
||||
"./startSyncServer": {
|
||||
"import": "./dist/startSyncServer.js",
|
||||
@@ -28,11 +28,11 @@
|
||||
"@effect/printer-ansi": "^0.34.5",
|
||||
"@effect/schema": "^0.71.1",
|
||||
"@effect/typeclass": "^0.25.5",
|
||||
"cojson": "workspace:0.15.2",
|
||||
"cojson-storage-sqlite": "workspace:0.15.2",
|
||||
"cojson-transport-ws": "workspace:0.15.2",
|
||||
"cojson": "workspace:0.15.6",
|
||||
"cojson-storage-sqlite": "workspace:0.15.6",
|
||||
"cojson-transport-ws": "workspace:0.15.6",
|
||||
"effect": "^3.6.5",
|
||||
"jazz-tools": "workspace:0.15.2",
|
||||
"jazz-tools": "workspace:0.15.6",
|
||||
"ws": "^8.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,5 +1,48 @@
|
||||
# jazz-tools
|
||||
|
||||
## 0.15.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a5ceaff: add Group.getDirectMembers to get only direct members of a group
|
||||
- cojson@0.15.6
|
||||
- cojson-storage@0.15.6
|
||||
- cojson-storage-indexeddb@0.15.6
|
||||
- cojson-transport-ws@0.15.6
|
||||
|
||||
## 0.15.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 23bfea5: Make all React Native dependencies in `jazz-tools` optional peer dependencies
|
||||
- e4ba23c: Fix type error with `RNQuickCrypto` that prevented using it as a `CryptoProvider`
|
||||
- 4b89838: Changes that verify the BetterAuth package for React also works with React Native and Expo apps
|
||||
- cojson@0.15.5
|
||||
- cojson-storage@0.15.5
|
||||
- cojson-storage-indexeddb@0.15.5
|
||||
- cojson-transport-ws@0.15.5
|
||||
|
||||
## 0.15.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [277e4d4]
|
||||
- cojson@0.15.4
|
||||
- cojson-storage@0.15.4
|
||||
- cojson-storage-indexeddb@0.15.4
|
||||
- cojson-transport-ws@0.15.4
|
||||
|
||||
## 0.15.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 45f73a7: fix image `originalSize` to be coherent with the highest resolution
|
||||
- Updated dependencies [535c460]
|
||||
- cojson-storage-indexeddb@0.15.3
|
||||
- cojson@0.15.3
|
||||
- cojson-storage@0.15.3
|
||||
- cojson-transport-ws@0.15.3
|
||||
|
||||
## 0.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -139,10 +139,9 @@
|
||||
},
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "0.15.2",
|
||||
"version": "0.15.6",
|
||||
"dependencies": {
|
||||
"@manuscripts/prosemirror-recreate-steps": "^0.1.4",
|
||||
"@op-engineering/op-sqlite": "^11.4.8",
|
||||
"@scure/base": "1.2.1",
|
||||
"@scure/bip39": "^1.3.0",
|
||||
"@tiptap/core": "^2.12.0",
|
||||
@@ -152,7 +151,6 @@
|
||||
"cojson-storage": "workspace:*",
|
||||
"cojson-storage-indexeddb": "workspace:*",
|
||||
"cojson-transport-ws": "workspace:*",
|
||||
"expo-sqlite": "15.2.9",
|
||||
"fast-myers-diff": "^3.2.0",
|
||||
"goober": "^2.1.16",
|
||||
"image-blob-reduce": "^4.1.0",
|
||||
@@ -163,10 +161,6 @@
|
||||
"prosemirror-schema-basic": "^1.2.2",
|
||||
"prosemirror-state": "^1.4.3",
|
||||
"prosemirror-transform": "^1.9.0",
|
||||
"react-native-fast-encoder": "^0.2.0",
|
||||
"react-native-mmkv": "^3.2.0",
|
||||
"react-native-nitro-modules": "0.25.2",
|
||||
"react-native-quick-crypto": "1.0.0-beta.16",
|
||||
"zod": "3.25.28"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -187,6 +181,8 @@
|
||||
"@testing-library/react": "16.2.0",
|
||||
"@types/react": "19.0.0",
|
||||
"@types/react-dom": "19.0.0",
|
||||
"@vitest/browser": "^3.2.4",
|
||||
"playwright": "^1.50.1",
|
||||
"tsup": "8.5.0",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
@@ -194,27 +190,39 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@bam.tech/react-native-image-resizer": "*",
|
||||
"@op-engineering/op-sqlite": "^11.4.8",
|
||||
"@react-native-community/netinfo": "*",
|
||||
"expo-file-system": "*",
|
||||
"expo-secure-store": "*",
|
||||
"expo-sqlite": "15.2.9",
|
||||
"react": "*",
|
||||
"react-dom": "*",
|
||||
"react-native": "*",
|
||||
"react-native-fast-encoder": "^0.2.0",
|
||||
"react-native-mmkv": "^3.2.0",
|
||||
"react-native-nitro-modules": "0.25.2",
|
||||
"react-native-quick-crypto": "1.0.0-beta.16",
|
||||
"svelte": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@bam.tech/react-native-image-resizer": {
|
||||
"optional": true
|
||||
},
|
||||
"expo-file-system": {
|
||||
"@op-engineering/op-sqlite": {
|
||||
"optional": true
|
||||
},
|
||||
"@react-native-community/netinfo": {
|
||||
"optional": true
|
||||
},
|
||||
"expo-file-system": {
|
||||
"optional": true
|
||||
},
|
||||
"expo-secure-store": {
|
||||
"optional": true
|
||||
},
|
||||
"expo-sqlite": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
@@ -224,8 +232,20 @@
|
||||
"react-native": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native-fast-encoder": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native-mmkv": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native-nitro-modules": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native-quick-crypto": {
|
||||
"optional": true
|
||||
},
|
||||
"svelte": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -8,7 +8,7 @@ import {
|
||||
} from "jazz-tools";
|
||||
import Pica from "pica";
|
||||
|
||||
let pica: Pica.Pica | undefined;
|
||||
let reducer: ImageBlobReduce.ImageBlobReduce | undefined;
|
||||
|
||||
/** @category Image creation */
|
||||
export async function createImage(
|
||||
@@ -18,113 +18,114 @@ export async function createImage(
|
||||
maxSize?: 256 | 1024 | 2048;
|
||||
},
|
||||
): Promise<Loaded<typeof ImageDefinition>> {
|
||||
// Inizialize Pica here to not have module side effects
|
||||
if (!pica) {
|
||||
pica = new Pica();
|
||||
}
|
||||
// Get the original size of the image
|
||||
const { width: originalWidth, height: originalHeight } =
|
||||
await getImageSize(imageBlobOrFile);
|
||||
|
||||
let originalWidth!: number;
|
||||
let originalHeight!: number;
|
||||
const Reducer = new ImageBlobReduce({ pica });
|
||||
Reducer.after("_blob_to_image", (env) => {
|
||||
originalWidth =
|
||||
(env as unknown as { orientation: number }).orientation & 4
|
||||
? env.image.height
|
||||
: env.image.width;
|
||||
originalHeight =
|
||||
(env as unknown as { orientation: number }).orientation & 4
|
||||
? env.image.width
|
||||
: env.image.height;
|
||||
return Promise.resolve(env);
|
||||
});
|
||||
const highestDimension = Math.max(originalWidth, originalHeight);
|
||||
|
||||
const placeholderDataURL = (
|
||||
await Reducer.toCanvas(imageBlobOrFile, { max: 8 })
|
||||
).toDataURL("image/png");
|
||||
// Calculate the sizes to resize the image to
|
||||
const resizes = [256, 1024, 2048, highestDimension]
|
||||
.filter((s) => s <= (options?.maxSize ?? highestDimension))
|
||||
.toSorted((a, b) => a - b);
|
||||
|
||||
// Get the highest resolution to use as final original size
|
||||
// In case of options.maxSize, it's not the originalWidth/Height
|
||||
const { width: finalWidth, height: finalHeight } = getNewDimensions(
|
||||
originalWidth,
|
||||
originalHeight,
|
||||
resizes.at(-1)!,
|
||||
);
|
||||
|
||||
const imageDefinition = ImageDefinition.create(
|
||||
{
|
||||
originalSize: [originalWidth, originalHeight],
|
||||
placeholderDataURL,
|
||||
},
|
||||
{ originalSize: [finalWidth, finalHeight] },
|
||||
options?.owner,
|
||||
);
|
||||
const owner = imageDefinition._owner;
|
||||
|
||||
const fillImageResolutions = async () => {
|
||||
const max256 = await Reducer.toBlob(imageBlobOrFile, { max: 256 });
|
||||
// Placeholder 8x8
|
||||
imageDefinition.placeholderDataURL =
|
||||
await getPlaceholderBase64(imageBlobOrFile);
|
||||
|
||||
if (originalWidth > 256 || originalHeight > 256) {
|
||||
const width =
|
||||
originalWidth > originalHeight
|
||||
? 256
|
||||
: Math.round(256 * (originalWidth / originalHeight));
|
||||
const height =
|
||||
originalHeight > originalWidth
|
||||
? 256
|
||||
: Math.round(256 * (originalHeight / originalWidth));
|
||||
|
||||
const binaryStream = await FileStream.createFromBlob(max256, owner);
|
||||
|
||||
imageDefinition[`${width}x${height}`] = binaryStream;
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
if (options?.maxSize === 256) return;
|
||||
|
||||
const max1024 = await Reducer.toBlob(imageBlobOrFile, { max: 1024 });
|
||||
|
||||
if (originalWidth > 1024 || originalHeight > 1024) {
|
||||
const width =
|
||||
originalWidth > originalHeight
|
||||
? 1024
|
||||
: Math.round(1024 * (originalWidth / originalHeight));
|
||||
const height =
|
||||
originalHeight > originalWidth
|
||||
? 1024
|
||||
: Math.round(1024 * (originalHeight / originalWidth));
|
||||
|
||||
const binaryStream = await FileStream.createFromBlob(max1024, owner);
|
||||
|
||||
imageDefinition[`${width}x${height}`] = binaryStream;
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
if (options?.maxSize === 1024) return;
|
||||
|
||||
const max2048 = await Reducer.toBlob(imageBlobOrFile, { max: 2048 });
|
||||
|
||||
if (originalWidth > 2048 || originalHeight > 2048) {
|
||||
const width =
|
||||
originalWidth > originalHeight
|
||||
? 2048
|
||||
: Math.round(2048 * (originalWidth / originalHeight));
|
||||
const height =
|
||||
originalHeight > originalWidth
|
||||
? 2048
|
||||
: Math.round(2048 * (originalHeight / originalWidth));
|
||||
|
||||
const binaryStream = await FileStream.createFromBlob(max2048, owner);
|
||||
|
||||
imageDefinition[`${width}x${height}`] = binaryStream;
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
if (options?.maxSize === 2048) return;
|
||||
|
||||
const originalBinaryStream = await FileStream.createFromBlob(
|
||||
imageBlobOrFile,
|
||||
owner,
|
||||
// Resizes for progressive loading
|
||||
for (let size of resizes) {
|
||||
// Calculate width and height respecting the aspect ratio
|
||||
const { width, height } = getNewDimensions(
|
||||
originalWidth,
|
||||
originalHeight,
|
||||
size,
|
||||
);
|
||||
|
||||
imageDefinition[`${originalWidth}x${originalHeight}`] =
|
||||
originalBinaryStream;
|
||||
};
|
||||
const image = await resize(imageBlobOrFile, width, height);
|
||||
|
||||
await fillImageResolutions();
|
||||
const binaryStream = await FileStream.createFromBlob(image, owner);
|
||||
imageDefinition[`${width}x${height}`] = binaryStream;
|
||||
}
|
||||
|
||||
return imageDefinition;
|
||||
}
|
||||
|
||||
async function getImageSize(
|
||||
imageBlobOrFile: Blob | File,
|
||||
): Promise<{ width: number; height: number }> {
|
||||
const { width, height } = await new Promise<{
|
||||
width: number;
|
||||
height: number;
|
||||
}>((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
resolve({ width: img.width, height: img.height });
|
||||
URL.revokeObjectURL(img.src);
|
||||
};
|
||||
img.onerror = () => {
|
||||
reject(new Error("Failed to load image"));
|
||||
URL.revokeObjectURL(img.src);
|
||||
};
|
||||
img.src = URL.createObjectURL(imageBlobOrFile);
|
||||
});
|
||||
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
async function getPlaceholderBase64(
|
||||
imageBlobOrFile: Blob | File,
|
||||
): Promise<string> {
|
||||
// Inizialize Reducer here to not have module side effects
|
||||
if (!reducer) {
|
||||
reducer = new ImageBlobReduce({ pica: new Pica() });
|
||||
}
|
||||
|
||||
const canvas = await reducer.toCanvas(imageBlobOrFile, { max: 8 });
|
||||
return canvas.toDataURL("image/png");
|
||||
}
|
||||
|
||||
async function resize(
|
||||
imageBlobOrFile: Blob | File,
|
||||
width: number,
|
||||
height: number,
|
||||
): Promise<Blob> {
|
||||
// Inizialize Reducer here to not have module side effects
|
||||
if (!reducer) {
|
||||
reducer = new ImageBlobReduce({ pica: new Pica() });
|
||||
}
|
||||
|
||||
return reducer.toBlob(imageBlobOrFile, { max: Math.max(width, height) });
|
||||
}
|
||||
|
||||
const getNewDimensions = (
|
||||
originalWidth: number,
|
||||
originalHeight: number,
|
||||
maxSize: number,
|
||||
) => {
|
||||
const width =
|
||||
originalWidth > originalHeight
|
||||
? maxSize
|
||||
: Math.round(maxSize * (originalWidth / originalHeight));
|
||||
|
||||
const height =
|
||||
originalHeight > originalWidth
|
||||
? maxSize
|
||||
: Math.round(maxSize * (originalHeight / originalWidth));
|
||||
|
||||
return { width, height };
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user