dashboard update

This commit is contained in:
raidendotai
2024-10-02 20:56:39 +01:00
parent b127f30724
commit 615ee55392
139 changed files with 19221 additions and 211 deletions

View File

@@ -34,7 +34,7 @@ Still not merged with key target features of the project, notably :
- code optimization
- [...]
Be patient :)
be patient :)
---
@@ -45,14 +45,14 @@ Be patient :)
* Open your terminal and run
```sh
npx @openinterface/cofounder -p "YourAppProjectName" -d "describe your app here" -a "(optional) design instructions"
npx @openinterface/cofounder
```
Follow the instructions. The installer
- will ask you for your keys
- setup dirs & start installs
- will start the local `cofounder/api` builder and server
- will start generating your app 🎉
- will open the web dashboard where you can create new projects (at `http://localhost:667` ) 🎉
```
note :
@@ -63,9 +63,16 @@ and can be used without limits during the current early alpha period
the full index will be available for local download on v1 release
```
## Run
```sh
# alternatively, you can make a new project without going through the dashboard
# by runing :
npx @openinterface/cofounder -p "YourAppProjectName" -d "describe your app here" -a "(optional) design instructions"
```
Your backend & vite+react web app will incrementally generate inside `./apps/{YourApp}`
## Run Generated Apps
- Your backend & vite+react web app will incrementally generate inside `./apps/{YourApp}`
Open your terminal in `./apps/{YourApp}` and run
```sh
@@ -75,9 +82,14 @@ npm i && npm run dev
It will start both the backend and vite+react, concurrently, after installing their dependencies
Go to `http://localhost:5173/` to open the web app 🎉
- From within the generated apps , you can use ⌘+K / Ctrl+K to iterate on UI components
[more details later]
## Notes
### Local API
### Dashboard & Local API
If you resume later and would like to iterate on your generated apps,
the local `./cofounder/api` server needs to be running to receive queries
@@ -88,12 +100,15 @@ You can (re)start the `local cofounder API` running the following command from `
npm run start
```
You can also generate new apps from the same env by running, from `./cofounder/api`, one of these command
The dashboard will open in `http://localhost:667`
```sh
npm run start -- -p "ProjectName" -f "some app description" -a "minimalist and spacious , light theme"
npm run start -- -p "ProjectName" -f "./example_description.txt" -a "minimalist and spacious , light theme"
```
- note: You can also generate new apps from the same env, without the the dashboard, by running, from `./cofounder/api`, one of these commands
```sh
npm run start -- -p "ProjectName" -f "some app description" -a "minimalist and spacious , light theme"
npm run start -- -p "ProjectName" -f "./example_description.txt" -a "minimalist and spacious , light theme"
```
### Concurrency
@@ -128,7 +143,7 @@ nodes:
and change the `op:LLM::GEN` parameter `concurrency` to a higher value
The default LLM concurrency is set to `1` so you can see what's happening in your console streams step by step - but you can increment it to `5`-`8`
The default LLM concurrency is set to `2` so you can see what's happening in your console streams step by step - but you can increment it depending on your api keys limits
---
@@ -155,4 +170,5 @@ archi/v1 is as follows :
* blocks.pm by Hexa Plugin (see `cofounder/api/system/presets`)
* google material
* figma core
* shadcn
* shadcn
- Dashboard node-based ui powered by [react flow](https://reactflow.dev/)

View File

@@ -29,9 +29,4 @@ DESIGNER_DESIGN_SYSTEM = "presets/shadcn" #"presets/shadcn"
SWARM_ENABLE = TRUE
# OPTIONAL
COFOUNDER_NICKNAME = "Cofounder"
#STATE_CLOUD = TRUE # persist on cloud (firebase + cloudstorage)
#FIREBASE_SERVICE_KEY_PATH = ""
#GOOGLECLOUDSTORAGE_SERVICE_KEY_PATH = ""
#GOOGLECLOUDSTORAGE_BUCKET = ""
COFOUNDER_NICKNAME = "Cofounder"

View File

@@ -1,3 +1,4 @@
db/
dump/
dist/
node_modules/

View File

@@ -229,32 +229,40 @@ async function build({ system }) {
data,
});
const sequenceLength = sequence.length;
if (context.sequence) {
console.dir({ "debug:build:context:sequence": context.sequence });
}
const resume_at = context?.sequence?.resume ? context.sequence.resume : 0;
let step_index = -1;
for (const s of sequence.entries()) {
const [index, step] = s;
events.log.sequence.emit(`sequence:step:start`, {
id: sequenceId,
index,
over: sequenceLength,
context,
data,
});
await Promise.all(
step.map(async (parallelfnId) => {
const response = await system.run({
id: parallelfnId,
context: { ...context, run: system.run },
data,
});
data = merge(data, response);
}),
);
events.log.sequence.emit(`sequence:step:end`, {
id: sequenceId,
index,
over: sequenceLength,
context,
data,
});
step_index++;
if (step_index >= resume_at) {
const [index, step] = s;
events.log.sequence.emit(`sequence:step:start`, {
id: sequenceId,
index,
over: sequenceLength,
context,
data,
});
await Promise.all(
step.map(async (parallelfnId) => {
const response = await system.run({
id: parallelfnId,
context: { ...context, run: system.run },
data,
});
data = merge(data, response);
}),
);
events.log.sequence.emit(`sequence:step:end`, {
id: sequenceId,
index,
over: sequenceLength,
context,
data,
});
}
}
events.log.sequence.emit(`sequence:end`, {
id: sequenceId,

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
cofounder/api/dist/favicon.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

30
cofounder/api/dist/index.html vendored Normal file
View File

@@ -0,0 +1,30 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
href="https://fonts.googleapis.com/css2?family=Karla:wght@100;200;300;400;500;600;700;800;900&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@100;200;300;400;500;600;700;800;900&display=swap"
rel="stylesheet"
/>
<title>Cofounder Dashboard</title>
<script type="module" crossorigin src="/assets/index-Ci0XSEHc.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-COffgP7k.css">
</head>
<body>
<div id="root"></div>
<script>
document.addEventListener("keydown", (event) => {
if (event.ctrlKey && event.key === "k") {
console.log("Ctrl+K pressed!");
event.preventDefault(); // Prevent doing a google search
}
});
</script>
</body>
</html>

View File

@@ -4,16 +4,19 @@
"@": "."
},
"scripts": {
"start": "node --loader esm-module-alias/loader --no-warnings server",
"start:npx": "npm i && node --loader esm-module-alias/loader --no-warnings server",
"dev:build": "node --loader esm-module-alias/loader --no-warnings build"
"start:npx": "npm i && nodemon --loader esm-module-alias/loader --no-warnings server.js",
"start": "nodemon --loader esm-module-alias/loader --no-warnings server.js"
},
"nodemonConfig": {
"ignore": [
"db/*"
]
},
"dependencies": {
"@anthropic-ai/sdk": "^0.27.3",
"@google-cloud/storage": "^7.12.1",
"@resvg/resvg-js": "^2.6.2",
"async-retry": "^1.3.3",
"cloudconvert": "^2.3.7",
"colormap": "^2.3.2",
"cors": "^2.8.5",
"deepmerge": "^4.3.1",
@@ -28,12 +31,16 @@
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"module-alias": "^2.2.3",
"open": "^10.1.0",
"openai": "^4.55.4",
"p-all": "^5.0.0",
"p-queue": "^8.0.1",
"sharp": "^0.33.4",
"slugify": "^1.6.6",
"socket.io": "^4.8.0",
"vectra": "^0.9.0",
"vite": "^5.4.8",
"vite-express": "^0.19.0",
"xml2js": "^0.6.2",
"yaml": "^2.5.0",
"yaml-js": "^0.3.1",

View File

@@ -1,13 +1,20 @@
import { Server } from "socket.io";
import utils from "@/utils/index.js";
import path from "path";
import { fileURLToPath } from "url";
import express from "express";
import cors from "cors";
import dotenv from "dotenv";
import yargs from "yargs";
import fs from "fs";
import yaml from "yaml";
import { hideBin } from "yargs/helpers";
import { merge } from "lodash-es";
import open, { openApp, apps } from "open";
import cofounder from "./build.js";
dotenv.config();
// -------------------------------------------------------------- HELPERS ------------------------
function _slugify(text) {
return text
.toString()
@@ -18,15 +25,17 @@ function _slugify(text) {
.replace(/^-+/, "") // Trim - from start
.replace(/-+$/, ""); // Trim - from end
}
// ----------------------------------------------------------------------------------------------------
// -------------------------------------------------------------- ARGS CASE ------------------------
// init project from argv
// to be called like : npm run start -- --p "some-project-name" --d "app description right"
// to be called like : npm run start -- -p "some-project-name" -d "app description"
const timestamp = Date.now();
const argv = yargs(hideBin(process.argv)).argv;
const newProject = {
const new_project = {
project:
(!argv.p && !argv.project) ||
_slugify(argv.p || argv.project).length === 0 ||
!_slugify(argv.p || argv.project).length ||
!_slugify(argv.p || argv.project).match(/[a-z0-9]/)
? `project-${timestamp}`
: _slugify(argv.p || argv.project),
@@ -34,33 +43,33 @@ const newProject = {
aesthetics: argv.aesthetics || argv.a || argv.aesthetic || false,
};
if (argv.file || argv.f) {
newProject.description = fs.readFileSync(argv.file || argv.f, "utf-8");
new_project.description = fs.readFileSync(argv.file || argv.f, "utf-8");
}
async function createNewProject() {
if (!newProject.description.length) {
async function create_new_project() {
if (!new_project.description.length) {
console.error(
'Error: -d "project description" is required and cannot be empty.',
);
process.exit(1);
}
console.log(
`\x1b[31minitialized generating app : ${newProject.project}\x1b[0m`,
`\x1b[31minitialized generating app : ${new_project.project}\x1b[0m`,
);
console.log(
`\x1b[34m(see ${process.env.EXPORT_APPS_ROOT}/${newProject.project}/README.md for more details)\x1b[0m` +
`\x1b[34m(see ${process.env.EXPORT_APPS_ROOT}/${new_project.project}/README.md for more details)\x1b[0m` +
`\n\x1b[38;5;37mto start app (api+frontend in parallel)` +
`\n\t> cd ${process.env.EXPORT_APPS_ROOT}/${newProject.project}` +
`\n\t> cd ${process.env.EXPORT_APPS_ROOT}/${new_project.project}` +
`\n\t> npm i && npm run dev\x1b[0m`,
);
const query = {
pm: {
details: {
text: `${newProject.project != `project-${timestamp}` ? "Project '" + newProject.project + "' :" : ""} ${newProject.description}`,
text: `${new_project.project != `project-${timestamp}` ? "Project '" + new_project.project + "' :" : ""} ${new_project.description}`,
attachments: [],
design: {
aesthetics: {
text: newProject.aesthetics,
text: new_project.aesthetics,
},
},
},
@@ -73,14 +82,16 @@ async function createNewProject() {
const data = await cofounder.system.run({
id: "op:PROJECT::STATE:LOAD",
context: {
project: newProject.project,
...context,
project: new_project.project,
},
data: {},
});
await cofounder.system.run({
id: `seq:project:init:v1:resume`,
context: {
project: newProject.project,
...context,
project: new_project.project,
},
data: merge(data, {
...query,
@@ -93,35 +104,176 @@ async function createNewProject() {
await cofounder.system.run({
id: `seq:project:init:v1`,
context: {
project: newProject.project,
...context,
project: new_project.project,
},
data: query,
});
}
// Call createNewProject if command args for init project are provided
if (newProject.project && newProject.description) {
createNewProject();
// Call create_new_project if command args for init project are provided
if (new_project.project && new_project.description) {
create_new_project();
}
// ----------------------------------------------------------------------------------------------------
// -------------------------------------------------------------- SERVER SETUP ------------------------
const app = express();
const PORT = process.env.PORT || 667;
app.use(cors());
app.use(express.json({ limit: "5mb" }));
app.use(express.json({ limit: "20mb" }));
// convert the current module's URL to a file path - necessary in ES modules to get the equivalent of __filename
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// serve static content from ./storage ; ie. for generated layout mockup images
app.use("/storage", express.static(path.join(__dirname, "db/storage")));
app.use(express.static(path.join(__dirname, "dist")));
app.use(/^\/(?!storage|api).*$/, express.static(path.join(__dirname, "dist")));
const server = app.listen(PORT, () => {
console.log(
"\x1b[33m\n> cofounder/api : server is running on port " + PORT + "\x1b[0m",
);
console.log(`> debug : open browser enabled : http://localhost:${PORT}/`);
open(`http://localhost:${PORT}/`);
});
// -------------------------------------------------------- SERVER REST API PATHS ------------------------
app.get("/api/ping", (req, res) => {
res.status(200).json({ message: "pong" });
});
app.get("/api/projects/list", (req, res) => {
fs.readdir("./db/projects", (err, files) => {
if (err) {
return res.status(500).json({ error: "> cant read projects directory" });
}
const projects = files
.filter((file) =>
fs.statSync(path.join("./db/projects", file)).isDirectory(),
)
.map((projectDir) => {
const yamlFilePath = path.join(
"./db/projects",
projectDir,
"state/pm/user/details.yaml",
);
if (fs.existsSync(yamlFilePath)) {
const fileContent = fs.readFileSync(yamlFilePath, "utf8");
const parsedData = yaml.parse(fileContent);
return { id: projectDir, data: parsedData.data || false };
}
return { id: projectDir, data: false };
});
res.status(200).json({ projects });
});
});
app.post("/api/utils/transcribe", async (req, res) => {
const uid = Math.random().toString(36).slice(2, 11); // Generate a random unique ID
const tempFilePath = path.join(__dirname, "db/storage/temp", `${uid}.webm`);
// Ensure the directory exists
fs.mkdirSync(path.dirname(tempFilePath), { recursive: true });
/*
app.post("/project/init", async (req, res) => {
try {
// see docs for steps
res.status(200).json({ end: true });
if (!req.body || !req.body.audio) {
throw new Error("No audio file uploaded");
}
const audioData = req.body.audio;
const audioBuffer = Buffer.from(audioData.split(",")[1], "base64");
// Write the audio buffer to the temporary path
fs.writeFileSync(tempFilePath, audioBuffer);
const { transcript } = await utils.openai.transcribe({ path: tempFilePath });
res.status(200).json({ transcript });
} catch (error) {
console.error(error);
res.status(500).json({ error: "failed to init project" });
console.error("Transcription error:", error);
res.status(500).json({ error: "Failed to transcribe audio" });
} finally {
// Delete the temporary file
fs.unlink(tempFilePath, (err) => {
if (err) console.error("Error deleting temporary file:", err);
});
}
});
*/
app.post("/api/projects/new", async (req, res) => {
const request = req.body;
/*
request : {
project? : "project-id" || {}
description: "",
aeshetics?: ""
}
*/
if (!request.description?.length) {
return res.status(500).json({ error: "> no project description provided" });
}
const timestamp = Date.now();
const new_project_query = {
project:
!request.project?.length ||
!_slugify(request.project).length ||
!_slugify(request.project).match(/[a-z0-9]/)
? `project-${timestamp}`
: _slugify(request.project),
description: request.description,
aesthetics: request.aesthetics?.length ? request.aesthetics : false,
};
const query = {
pm: {
details: {
text: `${new_project_query.project != `project-${timestamp}` ? "Project '" + new_project_query.project + "' :\n" : ""}${new_project_query.description}`,
attachments: [],
design: {
aesthetics: {
text: new_project_query.aesthetics,
},
},
timestamp,
},
},
};
// call async
cofounder.system.run({
id: `seq:project:init:v1`,
context: {
...context, // {streams}
project: new_project_query.project,
},
data: query,
});
res.status(200).json({ project: new_project_query.project });
});
app.post("/api/project/resume", async (req, res) => {
const { project } = req.body;
const resume_response = await resume_project({ project });
console.dir({ "debug:server:project/resume": resume_response });
setTimeout(async () => {
await cofounder.system.run({
id: `seq:project:init:v1`,
context: {
...context, // {streams}
project,
sequence: {
resume: resume_response.resume,
},
},
data: resume_response.data,
});
}, 2000);
res.status(200).json({ project });
});
const actions = {
// map action to function ; load means load project state before passing
@@ -138,8 +290,7 @@ const actions = {
*/
};
const actionsKeys = Object.keys(actions);
app.post("/project/actions", async (req, res) => {
app.post("/api/project/actions", async (req, res) => {
/*
in : {
project : `exampleproject`,
@@ -168,6 +319,7 @@ app.post("/project/actions", async (req, res) => {
: await cofounder.system.run({
id: "op:PROJECT::STATE:LOAD",
context: {
...context,
project,
},
data: {},
@@ -179,14 +331,323 @@ app.post("/project/actions", async (req, res) => {
res.status(500).json({ error: "failed to process" });
}
});
// ----------------------------------------------------------------------------------------------------
app.listen(PORT, () => {
console.log(
"\x1b[32m\ncofounder/api : server is running on port " + PORT + "\x1b[0m",
);
// -------------------------------------------------------------- SOCKET IO SETUP ------------------------
// socket.io instance attached to express
const io = new Server(server, {
cors: {
origin: "*",
methods: ["GET", "POST"],
},
});
const subscriptions = {};
const projects = {};
// will be sent inside context{} for system nodes to stream to at various steps
const streams = {
start: async ({ project, key, meta }) => {
/*
project , key , meta { name , desc }
*/
// console.dir({"debug:context:streams" : {subscriptions}} , {depth:null})
if (subscriptions[project])
io
.to(subscriptions[project])
.emit("stream$start", { timestamp: Date.now(), key, meta });
},
write: async ({ project, key, data }) => {
if (subscriptions[project])
io.to(subscriptions[project]).emit("stream$data", {
key,
data,
});
},
end: async ({ project, key }) => {
if (subscriptions[project])
io
.to(subscriptions[project])
.emit("stream$end", { timestamp: Date.now(), key });
},
update: async ({ project, key, data }) => {
if (!subscriptions[project]) return;
// console.log('> debug:streams:update :', key)
// ------------ helpers --------------------------------------------------------
if (key.includes("webapp.")) {
// reconstruct state
let projectSubState = {};
const keys = key.split(".");
let newStateUpdate = projectSubState;
keys.forEach((k, index) => {
if (!newStateUpdate[k]) {
newStateUpdate[k] = index === keys.length - 1 ? data : {};
} else if (index === keys.length - 1) {
newStateUpdate[k] = merge(newStateUpdate[k], data);
}
newStateUpdate = newStateUpdate[k];
});
const new_update_data = {};
let mergedKey;
Object.keys(projectSubState.webapp).map((_type) => {
// _type : react || layout
Object.keys(projectSubState.webapp[_type]).map((_category) => {
// _category : root || store || views
Object.keys(projectSubState.webapp[_type][_category]).map((_id) => {
// _id : app || redux || GV_Whatever || ...
mergedKey = `webapp.${_type}.${_category}.${_id}`;
new_update_data[mergedKey] = {};
Object.keys(projectSubState.webapp[_type][_category][_id]).map(
(_version) => {
// _version : latest || {timestamp}
new_update_data[mergedKey][_version] =
projectSubState.webapp[_type][_category][_id][_version];
},
);
});
});
});
key = mergedKey;
data = new_update_data[mergedKey];
}
io.to(subscriptions[project]).emit("state$update", {
key,
data,
});
},
};
const context = { streams };
io.on("connection", async (socket) => {
console.log("> user connected : ", socket.id);
socket.on("subscribe", async (project) => {
console.log(`> user ${socket.id} subscribed to project : ${project}`);
if (!subscriptions[project]) {
subscriptions[project] = [];
}
subscriptions[project].push(socket.id);
try {
await load_project({ project });
io.to(subscriptions[project]).emit("state$load", {
timestamp: Date.now(),
state: projects[project],
});
} catch (e) {
console.error("> cofounder/api : server error : ", e);
}
/*
console.log("> ____debug : server : op:LLM::DEBUG : run");
const inference_debug = await cofounder.system.run({
id: "op:LLM::DEBUG:SIMULATE",
context: {
...context, // {streams}
project,
operation: {
key: "pm.prd",
meta: {
name: "PRD",
desc: "project requirements doc",
},
},
},
data: {}, // subscriptions[project]
});
const prd = inference_debug.generated;
await cofounder.system.run({
id: "op:PROJECT::STATE:UPDATE",
context: {
...context,
project,
},
data: {
operation: {
id: "pm:prd",
},
type: `end`,
content: {
key: "pm.prd",
data: prd,
},
},
});
const debug_webapp_data = yaml.parse(
fs.readFileSync(
`db/projects/foundermatchdevclone/state/webapp/code/react/views/GV_TopNav/versions/1726434529344.yaml`,
"utf8",
),
);
debug_webapp_data.data.tsx = `it be cool if this updated fr`;
await cofounder.system.run({
id: "op:PROJECT::STATE:UPDATE",
context: {
...context,
project,
},
data: {
operation: {
id: `webapp:react:views`,
refs: {
id: "GV_TopNav",
version: "1726434529344",
},
},
type: `end`,
content: {
key: debug_webapp_data.key,
data: debug_webapp_data.data,
},
},
});
console.log("> ____debug : server : op:LLM::DEBUG:2");
const demo_messages = [
{
role: "system",
content:
"You are a helpful assistant that provides information about product requirements. in markdown format;",
},
{
role: "user",
content:
"Can you help me outline the product requirements for our new project? It's a rizz helper",
},
];
await cofounder.system.run({
id: "op:LLM::GEN",
context: {
...context, // {streams}
project,
operation: {
key: 'pm.prd',
meta: {
name: "PRD",
desc: "product requirements document",
},
},
},
data: {
model: `gpt-4o-mini`, //`gpt-4o`,
messages: demo_messages,
preparser: `backticks`,
parser: false,
},
})
*/
});
socket.on("disconnect", () => {
console.log("> user disconnected : ", socket.id);
for (const project in subscriptions) {
subscriptions[project] = subscriptions[project].filter(
(id) => id !== socket.id,
);
}
});
});
const load_project = async ({ project }) => {
console.log("> load_project : start : ", project);
const fetchedProject = await utils.load.local({
project,
deconstructed: true,
});
const fetchedProjectState = fetchedProject.state;
const _project = fetchedProject.keymap || {};
let projectData = {};
Object.keys(_project)
.filter((key) => !key.startsWith("webapp."))
.map((key) => {
projectData[key] = _project[key];
});
if (fetchedProjectState.webapp) {
Object.keys(fetchedProjectState.webapp).map((_type) => {
// _type : react || layout
Object.keys(fetchedProjectState.webapp[_type]).map((_category) => {
// _category : root || store || views
Object.keys(fetchedProjectState.webapp[_type][_category]).map((_id) => {
// _id : app || redux || GV_Whatever || ...
const mergedKey = `webapp.${_type}.${_category}.${_id}`;
projectData[mergedKey] = {};
Object.keys(fetchedProjectState.webapp[_type][_category][_id]).map(
(_version) => {
// _version : latest || {timestamp}
projectData[mergedKey][_version] =
fetchedProjectState.webapp[_type][_category][_id][_version];
},
);
});
});
});
}
projects[project] = projectData;
console.dir({
load_project: project,
data_keys: `${Object.keys(projects[project]).join(" , ")}`,
});
// only use in resume ; else check data stored in projects[project]
return fetchedProjectState;
};
const seq_projectv1_dag = [
// dumped from makeDag() in ./build
[], // project setup , skip
["pm.prd"],
["pm.frd"],
["pm.frd", "pm.uxsmd"],
["db.schemas", "uxsitemap.structure"],
["db.postgres"],
["pm.brd"],
["backend.specifications.openapi", "backend.specifications.asyncapi"],
["backend.server.main"],
["pm.uxdmd"],
["uxdatamap.structure"],
["uxdatamap.views"],
["webapp.react.store.redux"],
["webapp.react.root.app"],
["webapp.react.app.views"], // views , latest
];
async function resume_project({ project }) {
const project_data = await load_project({ project });
const project_keys = Object.keys(projects[project]);
let previous_phase_index = -1;
for (let step of seq_projectv1_dag) {
previous_phase_index++;
if (step.length) {
if (
step.every((entry) => project_keys.some((key) => key.startsWith(entry)))
) {
continue;
} else {
break;
}
}
}
return {
data: project_data,
resume: previous_phase_index,
};
}
// example of how to stream to client (needs 3 steps)
const stream_to_client = async ({ project, key, meta }) => {
// if (!subscriptions[project]?.length) return;
console.log(`> starting stream for project ${project}`);
streams.start({ project, key, meta });
const chunkSize = 20; // Define the size of each chunk
let currentIndex = 0;
const interval = setInterval(() => {
const timestamp = Date.now();
const data = texts[key].slice(currentIndex, currentIndex + chunkSize); // send chunk by chunk
streams.write({ project, key, data });
currentIndex += chunkSize; // Move to the next chunk
if (currentIndex >= texts[key].length) {
clearInterval(interval);
streams.end({ project, key });
}
}, 100);
};
// ----------------------------------------------------------------------------------------------------
// -------------------------------------------------------- SERVER REST API FUNCTION CALLS ------------------------
async function _updateProjectPreferences({ request }) {
/*
in : {
@@ -204,7 +665,7 @@ async function _updateProjectPreferences({ request }) {
const { project, query } = request;
await cofounder.system.run({
id: "op:PROJECT::STATE:UPDATE",
context: { project },
context: { ...context, project },
data: {
operation: {
id: `settings:preferences:versions`,
@@ -266,14 +727,13 @@ async function _regenerateUiComponent({ request, data }) {
console.dir({ "debug:server:task:regen:ui": { request, task } });
await cofounder.system.run({
id: "WEBAPP:VIEW::GENERATE",
context: { project },
context: { ...context, project },
data: {
...data,
task,
},
});
}
async function _iterateUiComponent({ request, data }) {
console.dir({ "cofounder:api:server:iterate:ui": "starts" });
/*
@@ -329,10 +789,11 @@ async function _iterateUiComponent({ request, data }) {
console.dir({ "debug:server:task:regen:ui": { request, task } });
await cofounder.system.run({
id: "WEBAPP:VIEW::ITERATE",
context: { project },
context: { ...context, project },
data: {
...data,
task,
},
});
}
}
// ----------------------------------------------------------------------------------------------------

View File

@@ -131,7 +131,16 @@ you're a genius`,
const asyncapiStructure = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "backend.specifications.asyncapi",
meta: {
name: "asyncAPI",
desc: "asyncAPI specifications",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages,

View File

@@ -136,7 +136,16 @@ you're a genius`,
const openapiStructure = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "backend.specifications.openapi",
meta: {
name: "openAPI",
desc: "openAPI specifications",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages,

View File

@@ -286,7 +286,16 @@ now do the analysis , write the full working script and specify the dependencies
const { generated } = await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "backend.server.main",
meta: {
name: "Backend code",
desc: "backend server main",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages: messages,
@@ -317,6 +326,23 @@ now do the analysis , write the full working script and specify the dependencies
timestamp: Date.now(),
};
/*
await context.run({
id: "op:PROJECT::STATE:UPDATE",
context,
data: {
operation: {
id: "backend:server:main",
},
type: `end`,
content: {
key: "backend.server.main",
data: generatedServer,
},
},
});
*/
// call swarm/agument:external-apis without waiting ; it will iterate it finds any external api decorators and replace
generatedServer = {
...generatedServer,

View File

@@ -97,7 +97,16 @@ you're a genius`
const postgres = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "db.postgres",
meta: {
name: "DB Postgresql",
desc: "db postgres commands {tables,seed}",
},
},
},
data: {
model: `chatgpt-4o-latest`,//`gpt-4o`,
messages,

View File

@@ -112,7 +112,16 @@ you're a genius`
const schemas = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "db.schemas",
meta: {
name: "DB Schemas",
desc: "db tables schemas",
},
},
},
data: {
model: `chatgpt-4o-latest`,//`gpt-4o`,
messages,

View File

@@ -354,7 +354,7 @@ async function designerLayoutv1ViewGenerate({ context, data }) {
data: {
index: "layouts",
text: ragText,
amount: 4,
amount: 5,
},
})
).results
@@ -378,26 +378,34 @@ async function designerLayoutv1ViewGenerate({ context, data }) {
rag.map(async (item) => {
const { url } = item.image_url;
try {
let metadata;
let buffer;
if (url.startsWith("data:image/")) {
// Handle base64 image
const base64Data = url.split("base64,")[1];
const buffer = Buffer.from(base64Data, "base64");
metadata = await sharp(buffer).metadata();
buffer = Buffer.from(base64Data, "base64");
} else if (url.startsWith("https://")) {
// Handle URL image
const response = await fetch(url);
if (!response.ok) {
console.err(`designer:layoutv1:rag : failed to fetch image`);
console.error(`designer:layoutv1:rag : failed to fetch image`);
return null;
}
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
metadata = await sharp(buffer).metadata();
buffer = Buffer.from(arrayBuffer);
} else {
// Invalid URL format, return null to filter out later
return null;
}
// Check image size using byteLength method
if (Buffer.byteLength(buffer) > 4.5 * 1024 * 1024) {
// 4.5 MB in bytes
console.error(`> skipping : image size exceeds 4.5 MB`);
return null;
}
const metadata = await sharp(buffer).metadata();
// Check image dimensions
if (
metadata.width >= 8000 ||
@@ -409,13 +417,13 @@ async function designerLayoutv1ViewGenerate({ context, data }) {
}
return item;
} catch (error) {
console.error(`> skipping : error processing RAG image at ${url}:`, error);
console.error(`> skipping : error processing RAG image : `, error);
return null; // Return null if there's an error
}
}),
)
)
.filter((item) => item !== null)
.filter((item) => item)
.slice(0, 3); // fetched more than needed in case size filtered ; typically indexed landing pages dims can be too big
data.task.rag = rag;
@@ -442,7 +450,16 @@ async function designerLayoutv1ViewGenerate({ context, data }) {
const analysisPass = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: `designer.layoutv1.analysis.${view.id}`,
meta: {
name: `Designer Analysis { ${view.id} }`,
desc: "designer/layoutv1 task analysis",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages: analysisPassMessages,
@@ -458,7 +475,17 @@ async function designerLayoutv1ViewGenerate({ context, data }) {
const svgPass = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: `designer.layoutv1.mockup.${view.id}`,
meta: {
name: `Designer Mockup { ${view.id} }`,
desc: "designer/layoutv1 mockup generation",
},
cutoff: "```svg",
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages: svgPassMessages,
@@ -484,7 +511,7 @@ async function designerLayoutv1ViewGenerate({ context, data }) {
svg = await xml2js.parseStringPromise(response.svg, {
explicitArray: true,
});
console.dir({ "debug:designer:layoutv1:svg": svg }, { depth: null });
// console.dir({ "debug:designer:layoutv1:svg": svg }, { depth: null });
if (!svg.svg.rect.filter((item) => item.$?.primitiveId).length) {
console.error(`layout error : generated != task ; skipping`);
}
@@ -817,7 +844,7 @@ async function designerLayoutv1ViewIterate({ context, data }) {
data: {
index: "layouts",
text: ragText,
amount: 4,
amount: 5,
},
})
).results
@@ -841,26 +868,34 @@ async function designerLayoutv1ViewIterate({ context, data }) {
rag.map(async (item) => {
const { url } = item.image_url;
try {
let metadata;
let buffer;
if (url.startsWith("data:image/")) {
// Handle base64 image
const base64Data = url.split("base64,")[1];
const buffer = Buffer.from(base64Data, "base64");
metadata = await sharp(buffer).metadata();
buffer = Buffer.from(base64Data, "base64");
} else if (url.startsWith("https://")) {
// Handle URL image
const response = await fetch(url);
if (!response.ok) {
console.err(`designer:layoutv1:rag : failed to fetch image`);
console.error(`designer:layoutv1:rag : failed to fetch image`);
return null;
}
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
metadata = await sharp(buffer).metadata();
buffer = Buffer.from(arrayBuffer);
} else {
// Invalid URL format, return null to filter out later
return null;
}
// Check image size
if (Buffer.byteLength(buffer) > 4.5 * 1024 * 1024) {
// 4.5 MB in bytes
console.error(`> skipping : image size exceeds 4.5 MB`);
return null;
}
const metadata = await sharp(buffer).metadata();
// Check image dimensions
if (
metadata.width >= 8000 ||
@@ -872,10 +907,7 @@ async function designerLayoutv1ViewIterate({ context, data }) {
}
return item;
} catch (error) {
console.error(
`> skipping : error processing analysis RAG image at ${url}:`,
error,
);
console.error(`> skipping : error processing RAG image`, error);
return null; // Return null if there's an error
}
}),
@@ -902,7 +934,17 @@ async function designerLayoutv1ViewIterate({ context, data }) {
const svgPass = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: `designer.layoutv1.mockup.${view.id}`,
meta: {
name: `Designer Mockup { ${view.id} }`,
desc: "designer/layoutv1 mockup generation",
},
cutoff: "```svg",
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages: svgIterateMessages,
@@ -928,7 +970,7 @@ async function designerLayoutv1ViewIterate({ context, data }) {
svg = await xml2js.parseStringPromise(response.svg, {
explicitArray: true,
});
console.dir({ "debug:designer:layoutv1:svg": svg }, { depth: null });
// console.dir({ "debug:designer:layoutv1:svg": svg }, { depth: null });
if (!svg.svg.rect.filter((item) => item.$?.primitiveId).length) {
console.error(`layout error : generated != task ; skipping`);
}

View File

@@ -16,8 +16,31 @@ async function opLlmGen({ context, data }) {
*/
let { model, messages, preparser, parser, validate, query, stream } = data;
const { project, operation, streams } = context;
if (operation?.key && streams) {
await streams.start({
project,
key: operation.key,
meta: operation.meta,
});
stream = {
write: async (data) => {
streams.write({
project,
key: operation.key,
data,
});
},
cutoff: operation?.cutoff ? operation.cutoff : false,
};
}
if (!stream) stream = process.stdout;
if (process.env.COFOUNDER_NICKNAME?.length) {
messages[0].content = `you are : ${process.env.COFOUNDER_NICKNAME}\n${messages[0].content}`;
}
if (!preparser) {
preparser = async ({ text }) => {
return { text };
@@ -46,6 +69,13 @@ async function opLlmGen({ context, data }) {
stream,
});
if (operation && streams) {
await streams.end({
project,
key: operation.key,
});
}
const generated_pre = await preparser({ text }); // -> typically { text : "... extracted text ..." }
const generated_post = await parser({
generated: generated_pre,
@@ -115,8 +145,72 @@ async function opLlmVectorize({ context, data }) {
};
}
async function opLlmDebugSimulate({ context, data }) {
/*
debug : simulate a stream
*/
const { project, operation } = context;
console.dir(
{
opLlmDebugSimulate: { context, data },
},
{ depth: null },
);
const text_demo = `
# Deleuze & Guattari
Gilles Deleuze (1925-1995) was a French philosopher known for his influential works in metaphysics, aesthetics, and political theory. His ideas have significantly impacted various fields, including literature, film, and art.
## Key Concepts
### Rhizome
Deleuze, along with Félix Guattari, introduced the concept of the **rhizome** in their work *A Thousand Plateaus*. Unlike traditional tree-like structures of knowledge, a rhizome represents a non-hierarchical and interconnected model of thought. It emphasizes multiplicity and the idea that any point can connect to any other point.
### Difference and Repetition
In his book *Difference and Repetition*, Deleuze challenges the notion of identity and sameness. He argues that difference is fundamental to understanding reality, and repetition is not merely a return of the same but a process that produces new meanings.
### Becoming
Deleuze's notion of **becoming** refers to the process of transformation and change. It suggests that identity is not fixed but is always in a state of flux, influenced by various factors and experiences.
## Conclusion
Deleuze's philosophy encourages us to think beyond binary oppositions and embrace complexity. His work continues to inspire contemporary thought and artistic practices, making him a pivotal figure in modern philosophy.
`;
await context.streams.start({
project,
key: operation.key,
meta: operation.meta,
});
const chunkSize = 20; // Define the size of each chunk
let currentIndex = 0;
while (currentIndex < text_demo.length) {
const data = text_demo.slice(currentIndex, currentIndex + chunkSize); // send chunk by chunk
context.streams.write({
project,
key: operation.key,
data,
});
currentIndex += chunkSize; // Move to the next chunk
await new Promise((resolve) => setTimeout(resolve, 100)); // Delay chunk by chunk
}
await context.streams.end({
project,
key: operation.key,
});
return {
generated: text_demo,
usage: {},
};
}
export default {
"op:LLM::GEN": opLlmGen,
"op:LLM::VECTORIZE": opLlmVectorize,
"op:LLM::VECTORIZE:CHUNK": opLlmVectorizeChunk,
"op:LLM::DEBUG:SIMULATE": opLlmDebugSimulate,
};

View File

@@ -5,7 +5,7 @@ import fs from "fs";
import yaml from "yaml";
import dotenv from "dotenv";
import fsextra from "fs-extra";
import { execSync } from "child_process";
import { exec, execSync } from "child_process";
dotenv.config();
/*
@@ -107,6 +107,12 @@ const config = {
],
};
async function run_npm_i(dependenciesRootPath) {
exec(`npm i`, {
stdio: "inherit",
cwd: dependenciesRootPath, // folder where package.json is
});
}
async function _exportOnSave({ context, data }) {
if (
!(
@@ -511,10 +517,31 @@ export default {{ID}};
console.log(
`\x1b[33m> dependencies updated for : ${dependenciesRootPath}\n> now running 'npm i' inside that folder\x1b[0m`,
);
execSync(`npm i`, {
stdio: "inherit",
cwd: dependenciesRootPath, // folder where package.json is
});
if (context.streams) {
await context.streams.start({
project,
key: "project.dependencies.install",
meta: {
name: "Install dependencies",
desc: "running 'npm i' in app dir",
},
});
await context.streams.write({
project,
key: "project.dependencies.install",
data: `command : 'npm i'\n\n---\n\nupdate :\n\n${yaml.stringify(task.data)}\n\n---\n\npath : ${dependenciesRootPath}`,
});
}
run_npm_i(dependenciesRootPath);
if (context.streams) {
await context.streams.end({
project,
key: "project.dependencies.install",
});
}
}
}),
);
@@ -584,7 +611,9 @@ async function opProjectStateUpdate({ context, data }) {
// query.data = { ...query.data, ...content }
}
if (content) query.data = { ...query.data, ...content };
console.dir({ "debug:op:project:state:update": { query } }, { depth: null });
console.dir({ "debug:op:project:state:update": { query } });
if (
process.env.STATE_LOCAL &&
JSON.parse(process.env.STATE_LOCAL.toLowerCase())
@@ -605,7 +634,14 @@ async function opProjectStateUpdate({ context, data }) {
}
}
fs.writeFileSync(localPath, yaml.stringify(query.data), "utf8");
if (context.streams) {
await context.streams.update({
project,
...query.data, // query.data : { key , data }
});
}
}
if (
process.env.STATE_CLOUD &&
JSON.parse(process.env.STATE_CLOUD.toLowerCase())

View File

@@ -70,7 +70,16 @@ you are a genius
const backendStructureRequirements = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "_pm.brd.requirements",
meta: {
name: "BRD Prepass",
desc: "backend structure requirements check",
},
},
},
data: {
model: `gpt-4o-mini`, //`gpt-4o`,
messages: backendPrompt,
@@ -295,7 +304,16 @@ you're a genius`,
const brd = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "pm.brd",
meta: {
name: "BRD",
desc: "backend requirements document",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages,

View File

@@ -107,7 +107,16 @@ you're a genius`,
const drd = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "pm.drd",
meta: {
name: "DRD",
desc: "database requirements document",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages,

View File

@@ -13,46 +13,39 @@ async function pmFrdAnalysis({ context, data }) {
role: "system",
content: `you are an expert product manager and product designer
your job is to consult the provided web app details & analysis PRD,
and create a full Features Requirements Document (FRD) for it
and create a Features Requirements Document (FRD) for it
the emphasis are user-facing features,
based on the expected features and different journeys of different users in the web app
your generated FRD is very detailed, comprehensive and covers absolutely 100% of everything required for the web app
your generated FRD is detailed, comprehensive and covers requirements for the web app
while conducting your FRD, ask yourself:
- am i covering all 100% the purpose and functions required for the app ?
- am i covering all 100% the expected features from all the users' perspectives? even the small details ?
- am i covering all 100% the user journeys ?
- am i covering all details that other product managers might have omitted from my analysis ?
- am i making sure what i am detailing in my FRDis absolutely 100% comprehensive and ready to be put into development without any alteration ?
- am i covering the purpose and functions required for the app ?
- am i covering the expected features from all the users' perspectives? even the small details ?
- am i covering the user journeys ?
- am i covering important details in my analysis ?
conduct and reply with a generated comprehensive perfect FRD document, markdown-formatted
the reply format should directly be a bulletpoints list of features items, each has 6 keys :
features:
name , category , featureId (/*like 'XXXX-01' format*/) , description , detailedDiscussion , extensiveDetailedBulletpoints
conduct and reply with a generated comprehensive FRD document, markdown-formatted
---
your FRD document will be directly put into development
the emphasis are user-facing features;
functional features + interface features to cover 100% of expected features of the web app, 100% of all possible user journeys
functional features + interface features to cover expected features of the web app
no need to bother with non-user-facing features such as security compliance, nor similar non-user-facing technical details
no need to bother with cases too advanced for the web app MVP features (ie. advanced analytics or multilingual or live support; ... unless specified in provided task ! )
emphasize user-facing features and core app MVP features
your reply will be directly transferred as the final FRD document
your reply will be directly transferred as the FRD document
so make sure the content is comprehensive and ensuing app UX is perfect as the genius you are
if an app name is not provided, make a fitting one for your analysis and FRD
emphasize user-facing features and core app MVP features
so do not put anything else in your reply besides the FRD DOC as parseable, valid well-formatted dpc
no extra comments or surrounding anything, only the VALID COMPREHENSIVE 100% COVERAGE AMAZING BEAUTIFUL FRD DOCUMENT
so do not put anything else in your reply besides the FRD DOC as parseable, valid well-formatted markdown doc
your reply should start with : "\`\`\`markdown" and end with "\`\`\`"
you will be tipped $999 you are a genius`,
@@ -73,10 +66,6 @@ ${prd}
{
role: "user",
content: `implement the Features Requirements Document (FRD)
it should span and cover all 100% of user-facing features and for all 100% of journeys required and will be directly pushed to development
absolutely no feature would be missing ; every detail and description for 100% of every feature required
it is expected to be 100% comprehensive and super detailed
you're a genius`,
},
];
@@ -84,7 +73,16 @@ you're a genius`,
const frd = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "pm.frd",
meta: {
name: "FRD",
desc: "features requirements document",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages,

View File

@@ -94,7 +94,16 @@ you are a genius`,
const prd = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "pm.prd",
meta: {
name: "PRD",
desc: "product requirements document",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages,

View File

@@ -186,7 +186,16 @@ you're a genius`,
const uxdmd = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "pm.uxdmd",
meta: {
name: "UXDMD",
desc: "ux datamap document",
},
},
},
data: {
model: `chatgpt-4o-latest`, // `chatgpt-4o-latest`,//`gpt-4o`,
messages,

View File

@@ -84,9 +84,17 @@ ask yourself:
---
important :
> do not many any "Container" views (like some GV_GlobalContainer or something) ; DO NOT make any container views to contain other views inside of them !
only make unique UV_* or GV_* shared views : views that serve a functional purpose ; not container views !
> GV_* shared views are INDEPENDENT from UV_* !
UV_* views DO NOT INTEGRATE GV_* views inside them !
they simply share screen space !!
do not make any UV_* functionality dependent on GV_*
do not make any GV_* functionality dependent on GV_*
they are independent , they do not include each other , shared simply means sharing screen space , not functionalities !
---
your reply will be directly transferred as the final UX Sitemap Analysis Document, so do not put anything else in your reply besides the UX Sitemap analysis document
@@ -124,7 +132,16 @@ you're a genius`,
const uxsmd = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "pm.uxsmd",
meta: {
name: "UXSMD",
desc: "ux sitemap document",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages,

View File

@@ -309,7 +309,16 @@ async function swarmAugmentBackendExternalapis({ context, data }) {
const analysisPass = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "swarm.augment.analysis",
meta: {
name: "Swarm Post-Gen Check",
desc: "analysis pass",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages: messagesAnalysis,
@@ -324,7 +333,16 @@ async function swarmAugmentBackendExternalapis({ context, data }) {
const messagesImplementMerge = await promptImplementMerge({ context, data });
const { generated } = await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "swarm.augment.implement",
meta: {
name: "Swarm Code Update",
desc: "implement changes & merge",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages: messagesImplementMerge,

View File

@@ -151,7 +151,16 @@ ${uxdmd}
const uxdatamapStructure = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "uxdatamap.structure",
meta: {
name: "UX Datamap Structure",
desc: "define the app's data architecture",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`chatgpt-4o-latest`, // `chatgpt-4o-latest`,//`gpt-4o`,
messages,
@@ -227,7 +236,7 @@ async function uxDatamapViews({ context, data }) {
});
});
}
const GVs = Object.keys(uxsitemap?.structure?.views?.shared) || {};
if (GVs.length) {
tasks.push({
@@ -470,7 +479,16 @@ you're a genius`,
const views = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "uxdatamap.views",
meta: {
name: "UX Datamap Views",
desc: "define views <> data architecture",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`chatgpt-4o-latest`, // `chatgpt-4o-latest`,//`gpt-4o`,
messages,

View File

@@ -93,7 +93,16 @@ You're a genius do a great job`,
const uxsitemapStructure = (
await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: "uxsitemap.structure",
meta: {
name: "UX Sitemap Structure",
desc: "define sitemap <> views architecture",
},
},
},
data: {
model: `chatgpt-4o-latest`, // `chatgpt-4o-latest`,//`gpt-4o`,
messages,

View File

@@ -10,8 +10,8 @@ async function promptRoot({ context, data }) {
const { uxsitemap, uxdatamap, webapp } = data;
const viewsImportHead = [
...( Object.keys(uxsitemap?.structure?.views?.shared) || {} ),
...( Object.keys(uxsitemap?.structure?.views?.unique) || {} ),
...(Object.keys(uxsitemap?.structure?.views?.shared) || {}),
...(Object.keys(uxsitemap?.structure?.views?.unique) || {}),
]
.map((viewId) => {
return `import ${viewId} from '@/components/views/${viewId}.tsx';`;
@@ -194,7 +194,16 @@ async function webappRootGenerate({ context, data }) {
const { generated } = await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: `webapp.react.root.app.latest`,
meta: {
name: "Webapp Root Component",
desc: "react App.tsx component code",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages: messages,

View File

@@ -194,7 +194,16 @@ async function webappStoreGenerate({ context, data }) {
const { generated } = await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: `webapp.react.store.redux.latest`,
meta: {
name: "Webapp Data Store",
desc: "redux data store component code",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages: messages,

View File

@@ -16,7 +16,7 @@ async function webappViewGenerateMulti({ context, data }) {
};
const tasks = [
...( Object.keys(views?.unique) || {} ).map((viewId) => {
...(Object.keys(views?.unique) || {}).map((viewId) => {
return {
task: {
type: "view",
@@ -28,7 +28,7 @@ async function webappViewGenerateMulti({ context, data }) {
},
};
}),
...( Object.keys(views?.shared) || {} ).map((viewId) => {
...(Object.keys(views?.shared) || {}).map((viewId) => {
return {
task: {
type: "view",
@@ -502,7 +502,16 @@ async function webappViewGenerate({ context, data }) {
const { generated } = await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: `webapp.react.views.${view.id}.latest`,
meta: {
name: `Webapp View Implement { ${view.id} }`,
desc: "react view, functional pass",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages: messagesFunctional,
@@ -679,7 +688,16 @@ async function webappViewGenerate({ context, data }) {
const { generated } = await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: `webapp.react.views.${view.id}.latest`,
meta: {
name: `Webapp View Redesign { ${view.id} }`,
desc: "react view, redesign pass",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages: messagesRedesign,
@@ -1222,7 +1240,16 @@ async function webappViewIterate({ context, data }) {
const { generated } = await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: `webapp.react.views.${view.id}.latest`,
meta: {
name: `Webapp View Iterate { ${view.id} }`,
desc: "react view iteration , code only",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages: promptMessagesNoDesigner,
@@ -1377,7 +1404,16 @@ async function webappViewIterate({ context, data }) {
const { generated } = await context.run({
id: "op:LLM::GEN",
context,
context: {
...context, // {streams , project}
operation: {
key: `webapp.react.views.${view.id}.latest`,
meta: {
name: `Webapp View Iterate { ${view.id} }`,
desc: "react view iteration , with designer",
},
},
},
data: {
model: `chatgpt-4o-latest`, //`gpt-4o`,
messages: mesagesIterateWithDesigner,

View File

@@ -13,7 +13,7 @@ nodes:
- generated
- usage
queue:
concurrency: 1
concurrency: 2
op:LLM::VECTORIZE:
desc: "{texts} -> {vectors}"
in:
@@ -31,3 +31,5 @@ nodes:
- usage
queue:
concurrency: 50
op:LLM::DEBUG:SIMULATE:
desc: "simulate llm streams for dev"

View File

@@ -78,17 +78,33 @@ async function inference({
max_tokens: 8192,
messages: converted.messages,
});
let text = "";
let usage = {};
let cutoff_reached = false;
let chunks_buffer = "";
let chunks_iterator = 0;
const chunks_every = 5;
for await (const event of streaming) {
if (
event.type === "content_block_delta" &&
event.delta.type === "text_delta"
) {
const content = event.delta.text;
stream.write(content);
text += content;
if (content) {
text += content;
chunks_buffer += content;
chunks_iterator++;
if (stream?.cutoff) {
if (!cutoff_reached && text.includes(stream.cutoff)) {
cutoff_reached = true;
}
}
if (!(chunks_iterator % chunks_every)) {
stream.write(!cutoff_reached ? chunks_buffer : " ...");
chunks_buffer = "";
}
}
}
}
stream.write("\n");

View File

@@ -40,7 +40,7 @@ async function readLocal({ project }) {
return db;
}
async function loadLocal({ project }) {
async function loadLocal({ project, deconstructed = false }) {
const db = await readLocal({ project });
let state = {};
/*
@@ -95,8 +95,9 @@ async function loadLocal({ project }) {
current = current[k];
});
});
return state;
if (!deconstructed) return state;
const keymap = Object.fromEntries(db.map(({ key, data }) => [key, data]));
return { state, keymap };
}
async function readCloud({ project }) {

View File

@@ -1,3 +1,4 @@
import fs from "fs";
import OpenAI from "openai";
import dotenv from "dotenv";
dotenv.config();
@@ -22,17 +23,33 @@ async function inference({
stream: true,
stream_options: { include_usage: true },
});
let text = "";
let usage = {};
let cutoff_reached = false;
let chunks_buffer = "";
let chunks_iterator = 0;
const chunks_every = 5;
for await (const chunk of streaming) {
const content = chunk.choices[0]?.delta?.content || "";
if (content) {
stream.write(content);
text += content;
chunks_buffer += content;
chunks_iterator++;
if (stream?.cutoff) {
if (!cutoff_reached && text.includes(stream.cutoff)) {
cutoff_reached = true;
}
}
if (!(chunks_iterator % chunks_every)) {
stream.write(!cutoff_reached ? chunks_buffer : " ...");
chunks_buffer = "";
}
}
if (chunk.usage) usage = { ...chunk.usage };
}
stream.write(`\n`);
return {
text,
usage: { model, ...usage },
@@ -55,7 +72,20 @@ async function vectorize({
};
}
async function transcribe({ path }) {
console.dir({ "debug:utils:openai:transcribe:received": { path } });
const response = await openai.audio.transcriptions.create({
file: fs.createReadStream(path),
model: "whisper-1",
});
console.dir({ "debug:utils:openai:transcribe": { path, response } });
return {
transcript: response.text,
};
}
export default {
inference,
vectorize,
transcribe,
};

View File

@@ -69,11 +69,12 @@ async function _render({
const pngData = resvg.render();
const pngBuffer = pngData.asPng();
// console.info('Original SVG Size:', `${resvg.width} x ${resvg.height}`)
/*
console.info(
"> utils.render svg size : ",
`${pngData.width}x${pngData.height}`,
);
*/
const uid = crypto.randomUUID();

View File

@@ -3,16 +3,20 @@ import fs from "fs";
import { PGlite } from "@electric-sql/pglite";
dotenv.config();
const postgres = new PGlite(`./db`);
const dbInitCommands = fs
.readFileSync(`./db.sql`, "utf-8")
.toString()
.split(/(?=CREATE |INSERT)/);
for (let cmd of dbInitCommands) {
console.dir({ "backend:db:init:command": cmd });
try {
await postgres.exec(cmd);
} catch (e) {
console.dir({ "backend:db:init:error": e });
const dbPath = `./db`;
if (!fs.existsSync(dbPath)) {
const postgres = new PGlite(dbPath);
const dbInitCommands = fs
.readFileSync(`./db.sql`, "utf-8")
.toString()
.split(/(?=CREATE TABLE |INSERT INTO)/);
for (let cmd of dbInitCommands) {
console.dir({ "backend:db:init:command": cmd });
try {
await postgres.exec(cmd);
} catch (e) {
console.dir({ "backend:db:init:error": e });
}
}
}

View File

@@ -1,7 +1,6 @@
import React from "react";
import { Route, Routes } from "react-router-dom";
import cofounder from "@/assets/cofounder.webp";
import { Button } from "@/components/ui/button";
const App: React.FC = () => {
return (

View File

@@ -110,7 +110,7 @@ export default {
*/
code = code.replaceAll(
`{COFOUNDER_LOCAL_API_BASE_URL}`,
`http://localhost:667`,
`http://localhost:667/api`,
);
if (path.includes(`src/App.tsx`)) {
return await editSectionsAndViews({ path, code });

View File

@@ -1,13 +0,0 @@
/*
cmdk <> genui-* <> relayer? store
*/
import { configureStore } from "@reduxjs/toolkit";
import { Provider } from "react-redux";
// Create a Redux store
const store = configureStore({
reducer: {}, // Add your reducers here
});
// Export the store as default
export default store;

View File

@@ -1,9 +0,0 @@
import { configureStore } from "@reduxjs/toolkit";
// Create a Redux store
const store = configureStore({
reducer: {}, // Empty reducer to prevent crashing
});
// Export the store as default
export default store;

26
cofounder/dashboard/.gitignore vendored Normal file
View File

@@ -0,0 +1,26 @@
public
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,2 @@
gits/
vectordb/

View File

@@ -0,0 +1,4 @@
{
"tabWidth": 1,
"useTabs": true
}

View File

@@ -0,0 +1 @@
[dashboard]

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": false,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}

View File

@@ -0,0 +1,26 @@
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";
export default tseslint.config({
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ["**/*.{ts,tsx}"],
ignores: ["dist"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
},
});

View File

@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
href="https://fonts.googleapis.com/css2?family=Karla:wght@100;200;300;400;500;600;700;800;900&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@100;200;300;400;500;600;700;800;900&display=swap"
rel="stylesheet"
/>
<title>Cofounder Dashboard</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script>
document.addEventListener("keydown", (event) => {
if (event.ctrlKey && event.key === "k") {
console.log("Ctrl+K pressed!");
event.preventDefault(); // Prevent doing a google search
}
});
</script>
</body>
</html>

9296
cofounder/dashboard/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,108 @@
{
"name": "dashboard",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@heroicons/react": "^2.1.5",
"@hookform/resolvers": "^3.9.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-aspect-ratio": "^1.1.0",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-context-menu": "^2.2.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-hover-card": "^1.1.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slider": "^1.2.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@reduxjs/toolkit": "^2.2.7",
"@trbn/jsoncanvas": "^1.0.6",
"@xyflow/react": "^12.3.0",
"axios": "^1.7.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"colormap": "^2.3.2",
"d3": "^7.9.0",
"date-fns": "^4.0.0-beta.1",
"embla-carousel-autoplay": "^8.3.0",
"embla-carousel-react": "^8.3.0",
"framer-motion": "^11.5.4",
"input-otp": "^1.2.4",
"lodash-es": "^4.17.21",
"lucide-react": "^0.439.0",
"next-themes": "^0.3.0",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-markdown": "^9.0.1",
"react-redux": "^9.1.2",
"react-resizable-panels": "^2.1.3",
"react-router-dom": "^6.26.0",
"react-speech-recognition": "^3.10.0",
"react-syntax-highlighter": "^15.5.0",
"react-tooltip": "^5.28.0",
"recharts": "^2.12.7",
"redux": "^5.0.1",
"redux-persist": "^6.0.0",
"redux-thunk": "^3.1.0",
"regenerator-runtime": "^0.14.1",
"rehype-highlight": "^7.0.0",
"rehype-raw": "^7.0.0",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.0",
"socket.io-client": "^4.7.5",
"sonner": "^1.5.0",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"use-react-screenshot": "^4.0.0",
"use-screenshot-hook": "^1.0.2",
"vaul": "^0.9.3",
"zod": "^3.23.8"
},
"devDependencies": {
"@eslint/js": "^9.8.0",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.1.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-syntax-highlighter": "^15.5.13",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"eslint": "^9.8.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"html2canvas": "^1.4.1",
"postcss": "^8.4.41",
"tailwindcss": "^3.4.8",
"typescript": "^5.5.3",
"typescript-eslint": "^8.0.0",
"vite": "^5.4.0"
}
}

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@@ -0,0 +1,97 @@
import React, { useCallback, useState, useEffect } from "react";
import { Route, Routes, useLocation } from "react-router-dom";
import cofounder from "@/assets/cofounder.webp";
import Sidebar from "@/components/views/sidebar";
import ProjectsList from "@/components/views/projects-list";
import ComponentsDesigner from "@/components/views/component-designer";
import Settings from "@/components/views/settings";
import Project from "@/components/views/project";
const App: React.FC = () => {
const [pingServer, setPingServer] = useState(false);
const [pingServerChecked, setPingServerChecked] = useState(false);
const location = useLocation();
const SERVER_LOCAL_URL = "http://localhost:667/api";
useEffect(() => {
const checkPingServer = async () => {
try {
const response = await fetch(`${SERVER_LOCAL_URL}/ping`);
if (response.ok) {
setPingServer(true);
} else {
setPingServer(false);
}
} catch (error) {
setPingServer(false);
}
setPingServerChecked(true);
};
checkPingServer();
}, []);
if (!pingServerChecked) return <></>;
return (
<>
{(pingServer && (
<>
<div className="flex h-screen">
{!location.pathname.startsWith("/project/") && <Sidebar />}
<div className="flex-1 overflow-auto">
<Routes>
<Route
path="/"
element={
<>
<div className="container text-white mx-auto w-full max-w-[90vw] xl:max-w-[60vw] p-12 mt-12 text-left whitespace-pre-line break-words">
<section className="pb-4 mb-4 text-center">
<a
href="https://github.com/raidendotai/cofounder"
target="_blank"
className="opacity-100 hover:opacity-90 duration-200"
>
<img
className="rounded rounded-xl max-w-[90vw] md:max-w-[35vw] mx-auto"
src={cofounder}
/>
</a>
</section>
<h2 className="mt-4 text-2xl opacity-50 font-light text-center uppercase">
early alpha release
</h2>
</div>
</>
}
/>
<Route
path="/projects"
element={
<div className="container text-white mx-auto w-full max-w-[90vw] xl:max-w-[80vw] p-6 mt-6 text-left whitespace-pre-line break-words">
<ProjectsList />
</div>
}
/>
<Route path="/project/:project" element={<Project />} />
<Route path="/playground/designer" element={<ComponentsDesigner />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</div>
</div>
</>
)) || (
<>
<div className="flex items-center justify-center h-screen w-full text-white">
<h1 className="text-2xl font-light opacity-50 whitespace-pre-wrap break-all">
{`{ local cofounder/api server at \`${SERVER_LOCAL_URL}\` not reachable }`}
<br />
<br />
{`>\tmake sure local cofounder server is launched\n\t( use \`npm run start\` in cofounder/api/ )`}
</h1>
</div>
</>
)}
</>
);
};
export default App;

View File

@@ -0,0 +1,29 @@
import React from "react";
import { Route, Routes } from "react-router-dom";
import cofounder from "@/assets/cofounder.webp";
import { Button } from "@/components/ui/button";
const App: React.FC = () => {
return (
<>
<div className="container mx-auto w-full xl:w-[60vw] p-12 mt-12 text-left whitespace-pre-line break-words">
<section className="pb-4 mb-4 text-center">
<a
href="https://github.com/raidendotai/cofounder"
target="_blank"
className="opacity-100 hover:opacity-90 duration-200"
>
<img
className="rounded rounded-xl md:max-w-[30vw] mx-auto"
src={cofounder}
/>
</a>
<h1 className="mt-8 text-2xl">Vite + React</h1>
<h1 className="mt-2">your app will update here as it generates</h1>
</section>
</div>
</>
);
};
export default App;

View File

@@ -0,0 +1,13 @@
import React, { useCallback, useState, useEffect } from "react";
import App from "@/App.tsx";
const AppWrapper: React.FC = () => {
return (
<>
{/*<Cmdl />*/}
<App />
</>
);
};
export default AppWrapper;

View File

@@ -0,0 +1,5 @@
#root {
max-width: 90vw;
margin: 0 auto;
padding: 2rem;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -0,0 +1,59 @@
import React from "react";
import { getBezierPath } from "@xyflow/react";
import { getEdgeParams } from "@/components/flow/helpers/utils.js";
function FloatingConnectionLine({
toX,
toY,
fromPosition,
toPosition,
fromNode,
}) {
if (!fromNode) {
return null;
}
const targetNode = {
id: "connection-target",
measured: {
width: 1,
height: 1,
},
internals: {
positionAbsolute: { x: toX, y: toY },
},
};
const { sx, sy } = getEdgeParams(fromNode, targetNode);
const [edgePath] = getBezierPath({
sourceX: sx,
sourceY: sy,
sourcePosition: fromPosition,
targetPosition: toPosition,
targetX: toX,
targetY: toY,
});
return (
<g>
<path
fill="none"
stroke="#222"
strokeWidth={1.5}
className="animated"
d={edgePath}
/>
<circle
cx={toX}
cy={toY}
fill="#fff"
r={3}
stroke="#222"
strokeWidth={1.5}
/>
</g>
);
}
export default FloatingConnectionLine;

View File

@@ -0,0 +1,38 @@
import { getBezierPath, useInternalNode } from "@xyflow/react";
import { getEdgeParams } from "@/components/flow/helpers/utils.js";
function FloatingEdge({ id, source, target, markerEnd, style }) {
const sourceNode = useInternalNode(source);
const targetNode = useInternalNode(target);
if (!sourceNode || !targetNode) {
return null;
}
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(
sourceNode,
targetNode,
);
const [edgePath] = getBezierPath({
sourceX: sx,
sourceY: sy,
sourcePosition: sourcePos,
targetPosition: targetPos,
targetX: tx,
targetY: ty,
});
return (
<path
id={id}
className="react-flow__edge-path"
d={edgePath}
markerEnd={markerEnd}
style={style}
/>
);
}
export default FloatingEdge;

View File

@@ -0,0 +1,10 @@
.floatingedges {
flex-direction: column;
display: flex;
flex-grow: 1;
height: 100%;
}
.floatingedges .react-flow__handle {
opacity: 0;
}

View File

@@ -0,0 +1,100 @@
import { Position, MarkerType } from "@xyflow/react";
// this helper function returns the intersection point
// of the line between the center of the intersectionNode and the target node
function getNodeIntersection(intersectionNode, targetNode) {
// https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a
const { width: intersectionNodeWidth, height: intersectionNodeHeight } =
intersectionNode.measured;
const intersectionNodePosition = intersectionNode.internals.positionAbsolute;
const targetPosition = targetNode.internals.positionAbsolute;
const w = intersectionNodeWidth / 2;
const h = intersectionNodeHeight / 2;
const x2 = intersectionNodePosition.x + w;
const y2 = intersectionNodePosition.y + h;
const x1 = targetPosition.x + targetNode.measured.width / 2;
const y1 = targetPosition.y + targetNode.measured.height / 2;
const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h);
const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h);
const a = 1 / (Math.abs(xx1) + Math.abs(yy1));
const xx3 = a * xx1;
const yy3 = a * yy1;
const x = w * (xx3 + yy3) + x2;
const y = h * (-xx3 + yy3) + y2;
return { x, y };
}
// returns the position (top,right,bottom or right) passed node compared to the intersection point
function getEdgePosition(node, intersectionPoint) {
const n = { ...node.internals.positionAbsolute, ...node };
const nx = Math.round(n.x);
const ny = Math.round(n.y);
const px = Math.round(intersectionPoint.x);
const py = Math.round(intersectionPoint.y);
if (px <= nx + 1) {
return Position.Left;
}
if (px >= nx + n.measured.width - 1) {
return Position.Right;
}
if (py <= ny + 1) {
return Position.Top;
}
if (py >= n.y + n.measured.height - 1) {
return Position.Bottom;
}
return Position.Top;
}
// returns the parameters (sx, sy, tx, ty, sourcePos, targetPos) you need to create an edge
export function getEdgeParams(source, target) {
const sourceIntersectionPoint = getNodeIntersection(source, target);
const targetIntersectionPoint = getNodeIntersection(target, source);
const sourcePos = getEdgePosition(source, sourceIntersectionPoint);
const targetPos = getEdgePosition(target, targetIntersectionPoint);
return {
sx: sourceIntersectionPoint.x,
sy: sourceIntersectionPoint.y,
tx: targetIntersectionPoint.x,
ty: targetIntersectionPoint.y,
sourcePos,
targetPos,
};
}
export function createNodesAndEdges() {
const nodes = [];
const edges = [];
const center = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
nodes.push({ id: "target", data: { label: "Target" }, position: center });
for (let i = 0; i < 8; i++) {
const degrees = i * (360 / 8);
const radians = degrees * (Math.PI / 180);
const x = 250 * Math.cos(radians) + center.x;
const y = 250 * Math.sin(radians) + center.y;
nodes.push({ id: `${i}`, data: { label: "Source" }, position: { x, y } });
edges.push({
id: `edge-${i}`,
target: "target",
source: `${i}`,
type: "floating",
markerEnd: {
type: MarkerType.Arrow,
},
});
}
return { nodes, edges };
}

View File

@@ -0,0 +1,196 @@
export default {
'code[class*="language-"]': {
color: "white",
fontFamily: "JetBrains Mono",
textAlign: "left",
whiteSpace: "pre",
wordSpacing: "normal",
wordBreak: "normal",
wordWrap: "normal",
MozTabSize: "4",
OTabSize: "4",
tabSize: "4",
WebkitHyphens: "none",
MozHyphens: "none",
msHyphens: "none",
hyphens: "none",
lineHeight: "25px",
fontSize: "0.8rem",
margin: "5px 0",
},
'pre[class*="language-"]': {
color: "white",
fontFamily: "JetBrains Mono",
textAlign: "left",
whiteSpace: "pre",
wordSpacing: "normal",
wordBreak: "normal",
wordWrap: "normal",
MozTabSize: "4",
OTabSize: "4",
tabSize: "4",
WebkitHyphens: "none",
MozHyphens: "none",
msHyphens: "none",
hyphens: "none",
lineHeight: "25px",
fontSize: "0.85rem",
margin: "0.5em 0",
background: "#2a2a2a",
padding: "1em",
overflow: "auto",
},
'pre[class*="language-"] *': {
fontFamily: "JetBrains Mono",
},
':not(pre) > code[class*="language-"]': {
color: "white",
background: "#0a143c",
padding: "0.1em",
borderRadius: "0.3em",
whiteSpace: "normal",
},
'pre[class*="language-"]::-moz-selection': {
textShadow: "none",
background: "rgba(29, 59, 83, 0.99)",
},
'pre[class*="language-"] ::-moz-selection': {
textShadow: "none",
background: "rgba(29, 59, 83, 0.99)",
},
'code[class*="language-"]::-moz-selection': {
textShadow: "none",
background: "rgba(29, 59, 83, 0.99)",
},
'code[class*="language-"] ::-moz-selection': {
textShadow: "none",
background: "rgba(29, 59, 83, 0.99)",
},
'pre[class*="language-"]::selection': {
textShadow: "none",
background: "rgba(29, 59, 83, 0.99)",
},
'pre[class*="language-"] ::selection': {
textShadow: "none",
background: "rgba(29, 59, 83, 0.99)",
},
'code[class*="language-"]::selection': {
textShadow: "none",
background: "rgba(29, 59, 83, 0.99)",
},
'code[class*="language-"] ::selection': {
textShadow: "none",
background: "rgba(29, 59, 83, 0.99)",
},
comment: {
color: "#6272a4",
fontStyle: "italic",
},
prolog: {
color: "#6272a4",
fontStyle: "italic",
},
cdata: {
color: "#6272a4",
fontStyle: "italic",
},
punctuation: {
color: "#f8f8f2",
},
".namespace": {
color: "#f8f8f2",
},
deleted: {
color: "#ff5555",
fontStyle: "italic",
},
symbol: {
color: "#ff79c6",
},
property: {
color: "#ff79c6",
},
tag: {
color: "#ff79c6",
},
operator: {
color: "#ff79c6",
},
keyword: {
color: "#ff79c6",
},
boolean: {
color: "#bd93f9",
},
number: {
color: "#bd93f9",
},
constant: {
color: "#8be9fd",
},
function: {
color: "#8be9fd",
},
builtin: {
color: "#8be9fd",
},
char: {
color: "#8be9fd",
},
selector: {
color: "#ff79c6",
fontStyle: "italic",
},
doctype: {
color: "#6272a4",
fontStyle: "italic",
},
"attr-name": {
color: "#50fa7b",
fontStyle: "italic",
},
inserted: {
color: "#50fa7b",
fontStyle: "italic",
},
string: {
color: "#f1fa8c",
},
url: {
color: "#f1fa8c",
},
entity: {
color: "#f1fa8c",
},
".language-css .token.string": {
color: "#f1fa8c",
},
".style .token.string": {
color: "#f1fa8c",
},
"class-name": {
color: "#8be9fd",
},
atrule: {
color: "#50fa7b",
},
"attr-value": {
color: "#f1fa8c",
},
regex: {
color: "#ffb86c",
},
important: {
color: "#ffb86c",
fontWeight: "bold",
},
variable: {
color: "#f8f8f2",
},
bold: {
fontWeight: "bold",
},
italic: {
fontStyle: "italic",
},
};

View File

@@ -0,0 +1,97 @@
export default {
meta: {
"pm.details": {
type: "pm",
name: "Details",
desc: "User-submitted Project Details",
},
"pm.prd": { type: "pm", name: "PRD", desc: "Product Requirements Document" },
"pm.frd": { type: "pm", name: "FRD", desc: "Features Requirements Document" },
"pm.drd": { type: "pm", name: "DRD", desc: "Database Requirements Document" },
"pm.brd": { type: "pm", name: "BRD", desc: "Backend Requirements Document" },
"pm.uxsmd": { type: "pm", name: "UXSMD", desc: "UX Sitemap Document" },
"pm.uxdmd": { type: "pm", name: "UXDMD", desc: "UX Datamap Document" },
"db.schemas": {
type: "db",
name: "DB/schemas",
desc: "Database Tables Schemas",
},
"db.postgres": {
type: "db",
name: "DB/postgres",
desc: "Database Postgresql Commands",
},
"backend.specifications.openapi": {
type: "backend",
name: "backend/define:openapi",
desc: "Backend Definition : openAPI",
},
"backend.specifications.asyncapi": {
type: "backend",
name: "backend/define:asyncapi",
desc: "Backend Definition : asyncAPI",
},
"backend.server.main": {
type: "backend",
name: "backend/server:main",
desc: "Backend Server : Main",
},
"uxsitemap.structure": {
type: "ux",
name: "ux/sitemap:structure",
desc: "UX Sitemap",
},
"uxdatamap.structure": {
type: "ux",
name: "ux/datamap:structure",
desc: "UX Datamap Structure",
},
"uxdatamap.views": {
type: "ux",
name: "ux/datamap:views",
desc: "UX Datamap Views",
},
"webapp.react.root": {
type: "webapp-structure",
name: "webapp/react:root",
desc: "Webapp App Root Component",
},
"webapp.react.store": {
type: "webapp-structure",
name: "webapp/react:store",
desc: "Webapp Data Store",
},
"webapp.react.views": {
type: "webapp-view",
name: "webapp/react:views",
desc: "Webapp View",
},
"settings.config.package": {
type: "ux",
name: "settings/config:package",
desc: "Dependencies & .env for package.json",
},
"settings.preferences.versions": {
type: "ux",
name: "settings/preferences:versions",
desc: "Components versions preferences",
},
},
types: {
"pm.details": "yaml", // is kinda ignored , hardcoded fix in "@/components/flow/nodes/cofounder-node"
pm: "markdown",
db: "yaml",
backend: "complex",
uxsitemap: "yaml",
uxdatamap: "yaml",
"webapp-structure": "complex",
"webapp-view": "complex",
settings: "yaml",
},
};

View File

@@ -0,0 +1,6 @@
.react-flow__node-cofounder_iframe {
background: #000000;
border: 1px solid #555;
border-radius: 5px;
text-align: left;
}

View File

@@ -0,0 +1,78 @@
.react-flow__node-cofounder_node {
background: #000000;
border: 1px solid #555;
border-radius: 5px;
text-align: left;
}
.markdown {
color: #ffffff; /* Text color for dark mode */
font-size: 0.975rem; /* 50% of typical size */
line-height: 1.3; /* Adjusted spacing between lines */
padding: 0.3rem; /* Reduced padding around the markdown container */
border-bottom: 1px solid #555; /* Separator border for sections */
margin-bottom: 1rem; /* Space below the markdown container */
-webkit-font-smoothing: antialiased;
}
.markdown h1 {
font-size: 1.5rem; /* 50% of typical h1 size */
margin: 0.3rem 0; /* Reduced margin above and below */
border-bottom: 1px solid #444; /* Separator border below h1 */
padding-bottom: 0.2rem; /* Padding below h1 */
}
.markdown h2 {
font-size: 1.25rem; /* 50% of typical h2 size */
margin: 0.3rem 0; /* Reduced margin above and below */
border-bottom: 1px solid #444; /* Separator border below h2 */
padding-bottom: 0.2rem; /* Padding below h2 */
}
.markdown h3 {
font-size: 1rem; /* 50% of typical h3 size */
margin: 0.2rem 0; /* Reduced margin above and below */
border-bottom: 1px solid #444; /* Separator border below h3 */
padding-bottom: 0.2rem; /* Padding below h3 */
}
.markdown p {
margin: 0.3rem 0; /* Reduced margin above and below paragraphs */
line-height: 1.75;
/* Added line spacing for better readability */
}
.markdown ul,
.markdown ol {
margin: 0.3rem 0; /* Reduced margin above and below lists */
padding-left: 1.2rem; /* Slightly reduced indentation for lists */
}
.markdown li {
margin: 0.1rem 0; /* Reduced margin above and below list items */
border-bottom: 1px solid #222;
}
.markdown blockquote {
border-left: 2px solid #555; /* Border for blockquotes */
padding-left: 0.5rem; /* Padding for blockquote text */
color: #cccccc; /* Lighter text color for blockquotes */
margin: 0.3rem 0; /* Reduced margin above and below blockquotes */
border-bottom: 1px solid #222;
}
.markdown code {
background-color: #222; /* Background for inline code */
color: #f8f8f2; /* Text color for inline code */
padding: 0.2rem 0.4rem; /* Padding for inline code */
margin: 0.1rem; /* Little margin for inline code */
border-radius: 3px; /* Rounded corners for inline code */
}
.markdown pre {
background-color: #111; /* Background for code blocks */
color: #f8f8f2; /* Text color for code blocks */
padding: 0.4rem; /* Reduced padding for code blocks */
border-radius: 5px; /* Rounded corners for code blocks */
overflow: auto; /* Allow scrolling for long code blocks */
margin: 0.3rem 0; /* Reduced margin above and below code blocks */
}

View File

@@ -0,0 +1,613 @@
import React, { memo, useState, useEffect, useRef } from "react";
import { Handle, Position } from "@xyflow/react";
import { useDispatch, useSelector } from "react-redux";
import yaml from "yaml";
import colormap from "colormap";
import Markdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogClose,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter";
import yaml_syntax from "react-syntax-highlighter/dist/esm/languages/prism/yaml";
import js_syntax from "react-syntax-highlighter/dist/esm/languages/prism/javascript";
import sql_syntax from "react-syntax-highlighter/dist/esm/languages/prism/sql";
import typescript_syntax from "react-syntax-highlighter/dist/esm/languages/prism/typescript";
import {
dracula,
duotoneDark,
vscDarkPlus,
zTouch,
} from "react-syntax-highlighter/dist/esm/styles/prism";
import zTouchEdit from "@/components/flow/helpers/zTouchEdit.js";
export default memo(({ data, isConnectable }) => {
const color_map = {
pm: "#FFA500",
db: "#000080",
backend: "#FF10F0",
ux: "#A020F0",
"webapp-structure": "#39FF14",
"webapp-view": "#05D9FF",
};
SyntaxHighlighter.registerLanguage("yaml", yaml_syntax);
SyntaxHighlighter.registerLanguage("js", js_syntax);
SyntaxHighlighter.registerLanguage("sql", sql_syntax);
SyntaxHighlighter.registerLanguage("typescript", typescript_syntax);
const dispatch = useDispatch();
const node_stream = !data.key.includes("webapp.react.views")
? useSelector((state: any) => state.project.streamEvents[data.key])
: useSelector(
(state: any) =>
state.project.streamEvents[`${data.key.split(".").slice(0, 4).join(".")}`],
);
const node_data = useSelector(
(state: any) => state.project.projectData[data.key],
);
let node_extra;
if (data.key.includes("webapp.react.views")) {
node_extra = useSelector(
(state: any) =>
state.project.projectData[data.key.replace(".react.", ".layout.")],
);
}
const streamContainerRef = useRef<HTMLDivElement>(null);
const [metaHeaderClass, setMetaHeaderClass] = useState("");
const [refresh, setRefresh] = useState(Date.now());
function getColor() {
return data?.meta?.type && color_map[data.meta.type]
? `[${color_map[data.meta.type]}]`
: "none";
}
function getLanguage() {
if (data.key === "db.postgres") return "sql";
return "yaml";
}
function getContent() {
if (!node_data) return "";
if (data.key === "db.postgres") return node_data;
return yaml.stringify(node_data);
}
const [selectedVersion, setSelectedVersion] = useState<string | boolean>(
false,
);
useEffect(() => {
if (data.key.includes("webapp.")) {
if (node_data && Object.keys(node_data).length) {
setSelectedVersion(Object.keys(node_data).reverse()[0]);
}
}
}, [node_data]);
/*
useEffect(() => {
setRefresh(Date.now())
}, [node_data , node_stream , node_extra]);
*/
function getMinifiedContent() {
// webapp component with versionning case
if (
data.key.includes("webapp.react.root") ||
data.key.includes("webapp.react.store")
) {
return (
<div className={`grid grid-cols-12`}>
<div className="col-span-9 grid grid-cols-2">
{(node_stream?.is_running && node_stream?.data && (
<pre
className="p-2 m-2 max-h-[40vh] overflow-auto whitespace-pre-wrap break-words"
style={{ fontFamily: "JetBrains Mono", fontWeight: 400 }}
>
<div className={`mx-2`}>
<div className="flex justify-center items-center p-4 m-4">
<div className="animate-spin rounded-full h-5 w-5 border-r border-[#aaa]"></div>
</div>
</div>
</pre>
)) || <></>}
<pre
ref={streamContainerRef}
className={`p-2 m-2 max-h-[40vh] overflow-auto whitespace-pre-wrap break-words duration-200
${node_stream?.is_running && node_stream?.data ? "" : "col-span-2"}`}
style={{ fontFamily: "JetBrains Mono", fontWeight: 400 }}
>
{(node_stream?.is_running && node_stream?.data && (
<>{node_stream.data.data}</>
)) || (
<>
{selectedVersion && node_data
? node_data[selectedVersion].tsx.slice(0, 300) + " ..."
: ""}
</>
)}
</pre>
</div>
{(node_stream?.is_running && node_stream?.data && (
<>
<div className="col-span-3 text-sm p-2 m-2">
<h3 className="py-2 mb-2 opacity-50">processing</h3>
</div>
</>
)) || (
<>
{(node_data && (
<div className="col-span-3 text-sm p-2 m-2">
<h3 className="border-b py-2 mb-2 border-[#333]">versions</h3>
<div className="grid">
{Object.entries(node_data)
.reverse()
.map(([version, item], index) => (
<a
key={index}
className={`rounded cursor-pointer p-2 mb-1 hover:bg-[#222] duration-200 ${
selectedVersion === version ? "bg-[#333]" : ""
} `}
onClick={() => {
setSelectedVersion(version);
}}
>
{version}
</a>
))}
</div>
</div>
)) ||
""}
</>
)}
</div>
);
}
// webapp view with layout and versionning case
if (data.key.includes("webapp.react.views")) {
return (
<div className={`grid grid-cols-12`}>
<div className={`col-span-9 grid grid-cols-2`}>
{(node_stream?.is_running && node_stream?.data && (
<pre
className="p-2 m-2 max-h-[40vh] overflow-auto whitespace-pre-wrap break-words"
style={{ fontFamily: "JetBrains Mono", fontWeight: 400 }}
>
<div className={`mx-2`}>
<div className="flex justify-center items-center p-4 m-4">
<div className="animate-spin rounded-full h-5 w-5 border-r border-[#aaa]"></div>
</div>
</div>
</pre>
)) || <></>}
<pre
ref={streamContainerRef}
className={`p-2 m-2 max-h-[40vh] overflow-auto whitespace-pre-wrap break-words duration-200
${node_stream?.is_running && node_stream?.data ? "" : "col-span-2"}
text-xs
`}
style={{ fontFamily: "JetBrains Mono", fontWeight: 400 }}
>
{(node_stream?.is_running && node_stream?.data && (
<>{node_stream.data.data}</>
)) || (
<>
{selectedVersion && node_data
? node_data[selectedVersion].tsx.slice(0, 300) + " ..."
: ""}
</>
)}
</pre>
</div>
{(node_stream?.is_running && node_stream?.data && (
<>
<div className="col-span-3 text-sm p-2 m-2">
<h3 className="py-2 mb-2 opacity-50">processing</h3>
</div>
</>
)) || (
<>
<div className="col-span-3 text-sm p-2 m-2 max-h-[40vh] overflow-auto">
<h3 className="border-b py-2 mb-2 border-[#333]">versions</h3>
<div className="grid">
{(node_data &&
Object.entries(node_data)
.reverse()
.map(([version, item], index) => (
<a
key={index}
className={`rounded cursor-pointer p-2 mb-1 hover:bg-[#222] duration-200 ${
selectedVersion === version ? "bg-[#333]" : ""
} `}
onClick={() => {
setSelectedVersion(version);
}}
>
{version}
</a>
))) || <></>}
</div>
</div>
</>
)}
{(node_extra &&
selectedVersion &&
node_extra[selectedVersion] &&
(node_extra[selectedVersion]?.render?.image?.url?.length ||
node_extra[selectedVersion]?.render?.image?.local?.length) && (
<div className="col-span-12 p-2 m-2 text-xs">
<img
title={`Design mockup reference generated for ${data.key}, version : ${selectedVersion}`}
alt={`Design mockup reference generated for ${data.key}, version : ${selectedVersion}`}
src={
node_extra[selectedVersion]?.render?.image?.url?.length
? node_extra[selectedVersion]?.render?.image?.url
: `http://localhost:667/storage/${node_extra[selectedVersion]?.render?.image?.local.split("/storage/")[1]}`
}
></img>
</div>
)) || <></>}
</div>
);
}
// default case
return (
<div
className={`grid ${node_stream?.is_running && node_stream?.data ? "grid-cols-2" : ""}`}
>
<div>
<pre
className="p-2 m-2 max-h-[25vh] overflow-auto whitespace-pre-wrap break-words"
style={{ fontFamily: "JetBrains Mono", fontWeight: 400 }}
>
{(node_stream?.is_running && node_stream?.data && (
<>
<div className="flex justify-center items-center p-4 m-4">
<div className="animate-spin rounded-full h-5 w-5 border-r border-[#aaa]"></div>
</div>
</>
)) || <></>}
{(node_data && (
<>
{typeof node_data === "string"
? node_data.slice(0, 100) + " ..."
: yaml.stringify(node_data).slice(0, 100) + " ..."}
</>
)) || <></>}
</pre>
</div>
{(node_stream?.is_running && node_stream?.data && (
<div>
<pre
ref={streamContainerRef}
className="p-2 m-2 max-h-[25vh] overflow-auto whitespace-pre-wrap break-words font-light"
>
{node_stream.data.data}
</pre>
</div>
)) || <></>}
</div>
);
}
function getExpandedContent() {
// webapp root/store case
if (
data.key.includes("webapp.react.root") ||
data.key.includes("webapp.react.store")
) {
if (!selectedVersion) return <></>;
return (
<>
{(selectedVersion && node_data && (
<pre className="rounded grid grid-cols-6 gap-2 rounded-lg p-2 text-white text-xs overflow-auto whitespace-pre-wrap break-words">
<SyntaxHighlighter
className="col-span-4 rounded bg-black text-xs"
language={"typescript"}
style={zTouchEdit}
wrapLines={true}
wrapLongLines={true}
>
{node_data[selectedVersion].tsx}
</SyntaxHighlighter>
<SyntaxHighlighter
className="col-span-2 rounded bg-black text-xs"
language={getLanguage()}
style={zTouchEdit}
wrapLines={true}
wrapLongLines={true}
>
{yaml.stringify({
...(node_data[selectedVersion]?.dependencies
? { dependencies: node_data[selectedVersion].dependencies }
: {}),
...(node_data[selectedVersion]?.analysis
? { analysis: node_data[selectedVersion].analysis }
: {}),
})}
</SyntaxHighlighter>
</pre>
)) ||
""}
</>
);
}
// webapp view case
if (data.key.includes("webapp.react.views")) {
if (!selectedVersion) return <></>;
return (
<>
{(selectedVersion && node_data && (
<pre className="rounded grid grid-cols-6 gap-2 rounded-lg p-2 text-white text-xs overflow-auto whitespace-pre-wrap break-words">
<SyntaxHighlighter
className="col-span-4 rounded bg-black text-xs"
language={"typescript"}
style={zTouchEdit}
wrapLines={true}
wrapLongLines={true}
>
{node_data[selectedVersion].tsx}
</SyntaxHighlighter>
<SyntaxHighlighter
className="col-span-2 rounded bg-black text-xs"
language={getLanguage()}
style={zTouchEdit}
wrapLines={true}
wrapLongLines={true}
>
{yaml.stringify({
...(node_data[selectedVersion]?.dependencies
? { dependencies: node_data[selectedVersion].dependencies }
: {}),
...(node_data[selectedVersion]?.analysis
? { analysis: node_data[selectedVersion].analysis }
: {}),
})}
</SyntaxHighlighter>
</pre>
)) ||
""}
</>
);
}
// server case
if (data.key === "backend.server.main") {
return (
<>
{(node_data && (
<pre className="rounded grid grid-cols-6 gap-2 rounded-lg p-2 text-white text-xs overflow-auto whitespace-pre-wrap break-words">
<SyntaxHighlighter
className="col-span-4 rounded bg-black text-xs"
language={"javascript"}
style={zTouchEdit}
wrapLines={true}
wrapLongLines={true}
>
{node_data.mjs}
</SyntaxHighlighter>
<SyntaxHighlighter
className="col-span-2 rounded bg-black text-xs"
language={getLanguage()}
style={zTouchEdit}
wrapLines={true}
wrapLongLines={true}
>
{yaml.stringify({
env: node_data.env,
dependencies: node_data.dependencies,
})}
</SyntaxHighlighter>
</pre>
)) ||
""}
</>
);
}
// default
return (
<>
<pre className="rounded rounded-lg p-2 text-white text-xs overflow-auto whitespace-pre-wrap break-words">
<SyntaxHighlighter
className="rounded bg-black text-xs"
language={getLanguage()}
style={zTouchEdit}
wrapLines={true}
wrapLongLines={true}
>
{getContent()}
</SyntaxHighlighter>
</pre>
</>
);
}
useEffect(() => {
if (streamContainerRef.current) {
streamContainerRef.current.scrollTop =
streamContainerRef.current.scrollHeight;
}
}, [node_stream]);
return (
<>
<div className="hidden bg-[#FFA500] bg-[#000080] bg-[#FF10F0] bg-[#A020F0] bg-[#05D9FF] bg-[#39FF14]">
_tw_manual_debugging_ _preload_all_colors_variations_so_it_builds_them
</div>
<div
className={`dark bg-[#0a0a0a] font-light ${node_stream?.is_running && node_stream?.data ? "max-w-[60vw] xl:max-w-[50vw]" : "max-w-[60vw] xl:max-w-[40vw]"} overflow-auto text-xs`}
key={refresh}
>
<div className="opacity-0">
<Handle
type="target"
id="top"
position={Position.Top}
style={{ background: "#555" }}
onConnect={(params) => console.log("handle onConnect", params)}
isConnectable={isConnectable}
/>
<Handle
type="target"
id="botom"
position={Position.Bottom}
style={{ background: "#555" }}
onConnect={(params) => console.log("handle onConnect", params)}
isConnectable={isConnectable}
/>
<Handle
type="target"
id="left"
position={Position.Left}
style={{ background: "#555" }}
onConnect={(params) => console.log("handle onConnect", params)}
isConnectable={isConnectable}
/>
<Handle
type="target"
id="right"
position={Position.Right}
style={{ background: "#555" }}
onConnect={(params) => console.log("handle onConnect", params)}
isConnectable={isConnectable}
/>
</div>
<div className="text-white rounded rounded-xl p-2 font-light">
<div
key={refresh}
className="text-base p-2 m-2 duration-200 rounded flex gap-2 items-start"
>
<div className={`h-8 w-8 m-2 rounded bg-${getColor()}`}></div>
<div>
<strong>{data.meta.name}</strong>
<br />
<span className="opacity-80">
{data.meta.desc}{" "}
{!data.key.includes("webapp.react.views")
? ""
: ` : ${data.key.split(".")[3]}`}
</span>
</div>
</div>
{getMinifiedContent()}
{(node_data && (
<Dialog>
<div className="flex justify-end border-t pt-2 my-2 border-[#222]">
<DialogTrigger asChild>
<Button variant="outline">View</Button>
</DialogTrigger>
</div>
<DialogContent className="font-light bg-white/10 backdrop-blur-md border-[#222] max-w-[90vw] h-[90vh] max-h-[90vh] overflow-auto p-8">
<DialogHeader className="text-[#aaa]">
<DialogTitle>Detailed View</DialogTitle>
<DialogDescription className="text-white text-lg whitespace-pre-wrap break-words font-light">
{data.key}{" "}
<strong className="text-base">
{selectedVersion ? `{ ${selectedVersion} }` : ""}
</strong>
</DialogDescription>
</DialogHeader>
{(data.meta.content_type === "markdown" && data.key != "pm.details" && (
<>
<div className="rounded rounded-lg p-12 m-2 bg-[#2a2a2a] text-white text-sm overflow-auto whitespace-pre-wrap break-words">
<Markdown className="markdown" remarkPlugins={[remarkGfm]}>
{node_data}
</Markdown>
</div>
</>
)) ||
getExpandedContent()}
<pre
className=""
style={{ fontFamily: "JetBrains Mono", fontWeight: 400 }}
>
{/*<SyntaxHighlighter language="yaml" style={vscDarkPlus} wrapLines={true} wrapLongLines={true} >
{yaml.stringify(node_data)}
</SyntaxHighlighter>
*/}
</pre>
<DialogFooter>
<DialogClose asChild>
<Button variant="">Back</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)) || <></>}
{/*
<input
className="nodrag rounded-lg p-1" style={{backgroundColor:bgColor}}
type="color"
onChange={(event) => {
const newColor = event.target.value;
setBgColor(newColor);
data.onChange(event);
}}
defaultValue={data.color}
style={{ cursor: 'pointer', width: '50px', height: '50px' }}
/>
*/}
</div>
<div className="opacity-0">
<Handle
type="source"
position={Position.Top}
id="top"
style={{ left: 10, background: "#555" }}
isConnectable={isConnectable}
/>
<Handle
type="source"
position={Position.Right}
id="right"
style={{ top: 10, background: "#555" }}
isConnectable={isConnectable}
/>
<Handle
type="source"
position={Position.Bottom}
id="bottom"
style={{ left: 10, background: "#555" }}
isConnectable={isConnectable}
/>
<Handle
type="source"
position={Position.Left}
id="left"
style={{ top: 10, background: "#555" }}
isConnectable={isConnectable}
/>
</div>
</div>
</>
);
});

View File

@@ -0,0 +1,6 @@
.react-flow__node-cofounder_terminal {
background: #000000;
border: 1px solid #555;
border-radius: 5px;
text-align: left;
}

View File

@@ -0,0 +1,6 @@
.react-flow__node-colorSelector {
background: #000000;
border: 1px solid #555;
border-radius: 5px;
text-align: center;
}

View File

@@ -0,0 +1,120 @@
import React, { memo, useState, useEffect } from "react";
import { Handle, Position } from "@xyflow/react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export default memo(({ data, isConnectable }) => {
const [bgColor, setBgColor] = useState(data.color);
const [randomString, setRandomString] = useState("");
useEffect(() => {
const interval = setInterval(() => {
setRandomString((prev) => {
const newString = prev + Math.random().toString(36).charAt(2);
if (newString.length > 500) {
clearInterval(interval);
return newString;
}
return newString;
});
}, 10);
return () => clearInterval(interval);
}, []);
return (
<div className="dark">
<Handle
type="target"
position={Position.Top}
style={{ background: "#555" }}
onConnect={(params) => console.log("handle onConnect", params)}
isConnectable={isConnectable}
/>
<div
className="text-white rounded rounded-xl p-2"
style={{
fontFamily: "JetBrains Mono",
fontWeight: 400,
background: bgColor,
}}
>
<div className="p-2 m-2 hover:bg-[#222] duration-200">
Custom Color Picker Node: <strong>{data.color}</strong>
</div>
<pre className="text-left m-4 p-4 bg-black text-white max-w-[30vw] whitespace-pre-line break-all overflow-auto">
random str : {randomString}
</pre>
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Edit Profile</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're done.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">
Name
</Label>
<Input id="name" defaultValue="Pedro Duarte" className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="username" className="text-right">
Username
</Label>
<Input id="username" defaultValue="@peduarte" className="col-span-3" />
</div>
</div>
<DialogFooter>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/*
<input
className="nodrag rounded-lg p-1" style={{backgroundColor:bgColor}}
type="color"
onChange={(event) => {
const newColor = event.target.value;
setBgColor(newColor);
data.onChange(event);
}}
defaultValue={data.color}
style={{ cursor: 'pointer', width: '50px', height: '50px' }}
/>
*/}
</div>
<Handle
type="source"
position={Position.Right}
id="a"
style={{ top: 10, background: "#555" }}
isConnectable={isConnectable}
/>
<Handle
type="source"
position={Position.Right}
id="b"
style={{ bottom: 10, top: "auto", background: "#555" }}
isConnectable={isConnectable}
/>
</div>
);
});

View File

@@ -0,0 +1,189 @@
import { MarkerType } from "@xyflow/react";
const PADDING_X = 150;
const DIST_X = PADDING_X * 2;
const PADDING_Y = 75;
const DIST_Y = PADDING_Y * 6;
export default {
metrics: {
PADDING_X,
PADDING_Y,
DIST_X,
DIST_Y,
},
layers: {
// pm layer , etc
},
nodes: {
"pm.details": { position: { x: DIST_X * 2, y: -DIST_Y * 2 } },
"pm.prd": { position: { x: 0, y: 0 } },
"pm.frd": { position: { x: DIST_X * 4, y: 0 } },
"pm.drd": { position: { x: DIST_X * 4, y: DIST_Y } },
"db.schemas": {
position: { x: PADDING_X * 2 + DIST_X * 5, y: DIST_Y + PADDING_Y },
},
"db.postgres": {
position: { x: PADDING_X * 2 + DIST_X * 6, y: DIST_Y + PADDING_Y * 2 },
},
"pm.brd": { position: { x: DIST_X * 5, y: PADDING_Y + DIST_Y * 2 } },
"backend.specifications.openapi": {
position: { x: DIST_X * 7, y: PADDING_Y + DIST_Y * 2 + PADDING_Y },
},
"backend.specifications.asyncapi": {
position: { x: DIST_X * 7, y: PADDING_Y + DIST_Y * 2.75 + PADDING_Y },
},
"backend.server.main": { position: { x: DIST_X * 9, y: DIST_Y * 2.5 } },
"pm.uxsmd": { position: { x: 0, y: DIST_Y } },
"pm.uxdmd": { position: { x: DIST_X * 2, y: DIST_Y } },
"uxsitemap.structure": {
position: { x: -DIST_X * 3.5, y: DIST_Y * 4.5 },
},
"uxdatamap.structure": {
position: { x: DIST_X * 1.5, y: DIST_Y * 2 + PADDING_Y },
},
"uxdatamap.views": {
position: { x: DIST_X * 3, y: DIST_Y * 2 + PADDING_Y * 2 },
},
"webapp.react.root": { position: { x: 0, y: DIST_Y * 3 + PADDING_Y * 2 } },
"webapp.react.store": {
position: { x: DIST_X * 3, y: DIST_Y * 3 + PADDING_Y * 2 },
},
"webapp.react.views": { position: { x: 0, y: DIST_Y * 6 } },
"settings.config.package": {
position: { x: DIST_X * 12, y: DIST_Y * 4 },
},
"settings.preferences.versions": {
position: { x: -DIST_X * 5, y: DIST_Y * 4.5 },
},
},
edges: [
...["pm.prd", "pm.frd"].map((target) => {
const source = "pm.details";
return {
id: `${source}-${target}`,
source,
target: target,
};
}),
...["pm.frd", "pm.drd", "pm.uxsmd", "pm.uxdmd", "pm.brd"].map((target) => {
const source = "pm.prd";
return {
id: `${source}-${target}`,
source,
target: target,
};
}),
...["pm.brd", "db.schemas", "db.postgres"].map((target) => {
const source = "pm.drd";
return {
id: `${source}-${target}`,
source,
target: target,
};
}),
...[
"backend.specifications.openapi",
"backend.specifications.asyncapi",
"pm.uxdmd",
].map((target) => {
const source = "pm.brd";
return {
id: `${source}-${target}`,
source,
target: target,
};
}),
...[
"pm.brd",
"backend.specifications.openapi",
"backend.specifications.asyncapi",
].map((source) => {
const target = "backend.server.main";
return {
id: `${source}-${target}`,
source,
target,
};
}),
...["pm.uxdmd", "uxsitemap.structure"].map((target) => {
const source = "pm.uxsmd";
return {
id: `${source}-${target}`,
source,
target: target,
};
}),
...["uxdatamap.structure", "uxdatamap.views"].map((target) => {
const source = "pm.uxdmd";
return {
id: `${source}-${target}`,
source,
target: target,
};
}),
...["webapp.react.root.app"].map((target) => {
const source = "uxsitemap.structure";
return {
id: `${source}-${target}`,
source,
target: target,
};
}),
...["webapp.react.root.app"].map((target) => {
const source = "uxdatamap.views";
return {
id: `${source}-${target}`,
source,
target: target,
};
}),
...["webapp.react.store.redux"].map((target) => {
const source = "uxdatamap.structure";
return {
id: `${source}-${target}`,
source,
target: target,
};
}),
...["backend.server.main", "uxsitemap.structure"].map((target) => {
const source = "settings.config.package";
return {
id: `${source}-${target}`,
source,
target: target,
};
}),
...["uxsitemap.structure"].map((target) => {
const source = "settings.preferences.versions";
return {
id: `${source}-${target}`,
source,
target: target,
};
}),
].map((item) => {
return {
animated: true,
style: { stroke: "#999" },
type: "floating",
markerEnd: {
type: MarkerType.ArrowClosed,
width: 30,
height: 30,
},
...item,
};
}),
};

View File

@@ -0,0 +1,791 @@
/* this gets exported as style.css and can be used for the default theming */
/* these are the necessary styles for React/Svelte Flow, they get used by base.css and style.css */
.react-flow {
direction: ltr;
--xy-edge-stroke-default: #b1b1b7;
--xy-edge-stroke-width-default: 1;
--xy-edge-stroke-selected-default: #555;
--xy-connectionline-stroke-default: #b1b1b7;
--xy-connectionline-stroke-width-default: 1;
--xy-attribution-background-color-default: rgba(255, 255, 255, 0.5);
--xy-minimap-background-color-default: #fff;
--xy-minimap-mask-background-color-default: rgb(240, 240, 240, 0.6);
--xy-minimap-mask-stroke-color-default: transparent;
--xy-minimap-mask-stroke-width-default: 1;
--xy-minimap-node-background-color-default: #e2e2e2;
--xy-minimap-node-stroke-color-default: transparent;
--xy-minimap-node-stroke-width-default: 2;
--xy-background-color-default: transparent;
--xy-background-pattern-dots-color-default: #91919a;
--xy-background-pattern-lines-color-default: #eee;
--xy-background-pattern-cross-color-default: #e2e2e2;
background-color: var(
--xy-background-color,
var(--xy-background-color-default)
);
--xy-node-color-default: inherit;
--xy-node-border-default: 1px solid #1a192b;
--xy-node-background-color-default: #fff;
--xy-node-group-background-color-default: rgba(240, 240, 240, 0.25);
--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, 0.08);
--xy-node-boxshadow-selected-default: 0 0 0 0.5px #1a192b;
--xy-node-border-radius-default: 3px;
--xy-handle-background-color-default: #1a192b;
--xy-handle-border-color-default: #fff;
--xy-selection-background-color-default: rgba(0, 89, 220, 0.08);
--xy-selection-border-default: 1px dotted rgba(0, 89, 220, 0.8);
--xy-controls-button-background-color-default: #fefefe;
--xy-controls-button-background-color-hover-default: #f4f4f4;
--xy-controls-button-color-default: inherit;
--xy-controls-button-color-hover-default: inherit;
--xy-controls-button-border-color-default: #eee;
--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08);
--xy-edge-label-background-color-default: #ffffff;
--xy-edge-label-color-default: inherit;
--xy-resize-background-color-default: #3367d9;
}
.react-flow.dark {
--xy-edge-stroke-default: #5c5c5c;
--xy-edge-stroke-width-default: 1;
--xy-edge-stroke-selected-default: #8a8a8a;
--xy-connectionline-stroke-default: #4a4a4a;
--xy-connectionline-stroke-width-default: 1;
--xy-attribution-background-color-default: rgba(100, 100, 100, 0.25);
--xy-minimap-background-color-default: #1a1a1a;
--xy-minimap-mask-background-color-default: rgba(80, 80, 80, 0.6);
--xy-minimap-mask-stroke-color-default: transparent;
--xy-minimap-mask-stroke-width-default: 1;
--xy-minimap-node-background-color-default: #3a3a3a;
--xy-minimap-node-stroke-color-default: transparent;
--xy-minimap-node-stroke-width-default: 2;
--xy-background-color-default: #1a1a1a;
--xy-background-pattern-dots-color-default: #616161;
--xy-background-pattern-lines-color-default: #141414;
--xy-background-pattern-cross-color-default: #525252;
--xy-node-color-default: #e0e0e0;
--xy-node-border-default: 1px solid #4a4a4a;
--xy-node-background-color-default: #2a2a2a;
--xy-node-group-background-color-default: rgba(200, 200, 200, 0.25);
--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, 0.08);
--xy-node-boxshadow-selected-default: 0 0 0 0.5px #b0b0b0;
--xy-handle-background-color-default: #d0d0d0;
--xy-handle-border-color-default: #2a2a2a;
--xy-selection-background-color-default: rgba(150, 150, 200, 0.08);
--xy-selection-border-default: 1px dotted rgba(150, 150, 200, 0.8);
--xy-controls-button-background-color-default: #3a3a3a;
--xy-controls-button-background-color-hover-default: #4a4a4a;
--xy-controls-button-color-default: #e0e0e0;
--xy-controls-button-color-hover-default: #ffffff;
--xy-controls-button-border-color-default: #6a6a6a;
--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08);
--xy-edge-label-background-color-default: #1a1a1a;
--xy-edge-label-color-default: #e0e0e0;
}
.react-flow__background {
background-color: var(
--xy-background-color,
var(--xy-background-color-props, var(--xy-background-color-default))
);
pointer-events: none;
z-index: -1;
}
.react-flow__container {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.react-flow__pane {
z-index: 1;
}
.react-flow__pane.draggable {
cursor: grab;
}
.react-flow__pane.dragging {
cursor: grabbing;
}
.react-flow__pane.selection {
cursor: pointer;
}
.react-flow__viewport {
transform-origin: 0 0;
z-index: 2;
pointer-events: none;
}
.react-flow__renderer {
z-index: 4;
}
.react-flow__selection {
z-index: 6;
}
.react-flow__nodesselection-rect:focus,
.react-flow__nodesselection-rect:focus-visible {
outline: none;
}
.react-flow__edge-path {
stroke: var(--xy-edge-stroke, var(--xy-edge-stroke-default));
stroke-width: var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));
fill: none;
}
.react-flow__connection-path {
stroke: var(
--xy-connectionline-stroke,
var(--xy-connectionline-stroke-default)
);
stroke-width: var(
--xy-connectionline-stroke-width,
var(--xy-connectionline-stroke-width-default)
);
fill: none;
}
.react-flow .react-flow__edges {
position: absolute;
}
.react-flow .react-flow__edges svg {
overflow: visible;
position: absolute;
pointer-events: none;
}
.react-flow__edge {
pointer-events: visibleStroke;
}
.react-flow__edge.selectable {
cursor: pointer;
}
.react-flow__edge.animated path {
stroke-dasharray: 5;
animation: dashdraw 0.5s linear infinite;
}
.react-flow__edge.animated path.react-flow__edge-interaction {
stroke-dasharray: none;
animation: none;
}
.react-flow__edge.inactive {
pointer-events: none;
}
.react-flow__edge.selected,
.react-flow__edge:focus,
.react-flow__edge:focus-visible {
outline: none;
}
.react-flow__edge.selected .react-flow__edge-path,
.react-flow__edge.selectable:focus .react-flow__edge-path,
.react-flow__edge.selectable:focus-visible .react-flow__edge-path {
stroke: var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default));
}
.react-flow__edge-textwrapper {
pointer-events: all;
}
.react-flow__edge .react-flow__edge-text {
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.react-flow__connection {
pointer-events: none;
}
.react-flow__connection .animated {
stroke-dasharray: 5;
animation: dashdraw 0.5s linear infinite;
}
svg.react-flow__connectionline {
z-index: 1001;
overflow: visible;
position: absolute;
}
.react-flow__nodes {
pointer-events: none;
transform-origin: 0 0;
}
.react-flow__node {
position: absolute;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
pointer-events: all;
transform-origin: 0 0;
box-sizing: border-box;
cursor: default;
}
.react-flow__node.selectable {
cursor: pointer;
}
.react-flow__node.draggable {
cursor: grab;
pointer-events: all;
}
.react-flow__node.draggable.dragging {
cursor: grabbing;
}
.react-flow__nodesselection {
z-index: 3;
transform-origin: left top;
pointer-events: none;
}
.react-flow__nodesselection-rect {
position: absolute;
pointer-events: all;
cursor: grab;
}
.react-flow__handle {
position: absolute;
pointer-events: none;
min-width: 5px;
min-height: 5px;
width: 6px;
height: 6px;
background-color: var(
--xy-handle-background-color,
var(--xy-handle-background-color-default)
);
border: 1px solid
var(--xy-handle-border-color, var(--xy-handle-border-color-default));
border-radius: 100%;
}
.react-flow__handle.connectingfrom {
pointer-events: all;
}
.react-flow__handle.connectionindicator {
pointer-events: all;
cursor: crosshair;
}
.react-flow__handle-bottom {
top: auto;
left: 50%;
bottom: 0;
transform: translate(-50%, 50%);
}
.react-flow__handle-top {
top: 0;
left: 50%;
transform: translate(-50%, -50%);
}
.react-flow__handle-left {
top: 50%;
left: 0;
transform: translate(-50%, -50%);
}
.react-flow__handle-right {
top: 50%;
right: 0;
transform: translate(50%, -50%);
}
.react-flow__edgeupdater {
cursor: move;
pointer-events: all;
}
.react-flow__panel {
position: absolute;
z-index: 5;
margin: 15px;
}
.react-flow__panel.top {
top: 0;
}
.react-flow__panel.bottom {
bottom: 0;
}
.react-flow__panel.left {
left: 0;
}
.react-flow__panel.right {
right: 0;
}
.react-flow__panel.center {
left: 50%;
transform: translateX(-50%);
}
.react-flow__attribution {
font-size: 10px;
background: var(
--xy-attribution-background-color,
var(--xy-attribution-background-color-default)
);
padding: 2px 3px;
margin: 0;
}
.react-flow__attribution a {
text-decoration: none;
color: #999;
}
@keyframes dashdraw {
from {
stroke-dashoffset: 10;
}
}
.react-flow__edgelabel-renderer {
position: absolute;
width: 100%;
height: 100%;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
left: 0;
top: 0;
}
.react-flow__viewport-portal {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.react-flow__minimap {
background: var(
--xy-minimap-background-color-props,
var(--xy-minimap-background-color, var(--xy-minimap-background-color-default))
);
}
.react-flow__minimap-svg {
display: block;
}
.react-flow__minimap-mask {
fill: var(
--xy-minimap-mask-background-color-props,
var(
--xy-minimap-mask-background-color,
var(--xy-minimap-mask-background-color-default)
)
);
stroke: var(
--xy-minimap-mask-stroke-color-props,
var(
--xy-minimap-mask-stroke-color,
var(--xy-minimap-mask-stroke-color-default)
)
);
stroke-width: var(
--xy-minimap-mask-stroke-width-props,
var(
--xy-minimap-mask-stroke-width,
var(--xy-minimap-mask-stroke-width-default)
)
);
}
.react-flow__minimap-node {
fill: var(
--xy-minimap-node-background-color-props,
var(
--xy-minimap-node-background-color,
var(--xy-minimap-node-background-color-default)
)
);
stroke: var(
--xy-minimap-node-stroke-color-props,
var(
--xy-minimap-node-stroke-color,
var(--xy-minimap-node-stroke-color-default)
)
);
stroke-width: var(
--xy-minimap-node-stroke-width-props,
var(
--xy-minimap-node-stroke-width,
var(--xy-minimap-node-stroke-width-default)
)
);
}
.react-flow__background-pattern.dots {
fill: var(
--xy-background-pattern-color-props,
var(
--xy-background-pattern-color,
var(--xy-background-pattern-dots-color-default)
)
);
}
.react-flow__background-pattern.lines {
stroke: var(
--xy-background-pattern-color-props,
var(
--xy-background-pattern-color,
var(--xy-background-pattern-lines-color-default)
)
);
}
.react-flow__background-pattern.cross {
stroke: var(
--xy-background-pattern-color-props,
var(
--xy-background-pattern-color,
var(--xy-background-pattern-cross-color-default)
)
);
}
.react-flow__controls {
display: flex;
flex-direction: column;
box-shadow: var(
--xy-controls-box-shadow,
var(--xy-controls-box-shadow-default)
);
}
.react-flow__controls.horizontal {
flex-direction: row;
}
.react-flow__controls-button {
display: flex;
justify-content: center;
align-items: center;
height: 26px;
width: 26px;
padding: 4px;
border: none;
background: var(
--xy-controls-button-background-color,
var(--xy-controls-button-background-color-default)
);
border-bottom: 1px solid
var(
--xy-controls-button-border-color-props,
var(
--xy-controls-button-border-color,
var(--xy-controls-button-border-color-default)
)
);
color: var(
--xy-controls-button-color-props,
var(--xy-controls-button-color, var(--xy-controls-button-color-default))
);
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.react-flow__controls-button svg {
width: 100%;
max-width: 12px;
max-height: 12px;
fill: currentColor;
}
.react-flow__edge.updating .react-flow__edge-path {
stroke: #777;
}
.react-flow__edge-text {
font-size: 10px;
}
.react-flow__node.selectable:focus,
.react-flow__node.selectable:focus-visible {
outline: none;
}
.react-flow__node-input,
.react-flow__node-default,
.react-flow__node-output,
.react-flow__node-group {
padding: 10px;
border-radius: var(
--xy-node-border-radius,
var(--xy-node-border-radius-default)
);
width: 150px;
font-size: 12px;
color: var(--xy-node-color, var(--xy-node-color-default));
text-align: center;
border: var(--xy-node-border, var(--xy-node-border-default));
background-color: var(
--xy-node-background-color,
var(--xy-node-background-color-default)
);
}
.react-flow__node-input.selectable:hover,
.react-flow__node-default.selectable:hover,
.react-flow__node-output.selectable:hover,
.react-flow__node-group.selectable:hover {
box-shadow: var(
--xy-node-boxshadow-hover,
var(--xy-node-boxshadow-hover-default)
);
}
.react-flow__node-input.selectable.selected,
.react-flow__node-input.selectable:focus,
.react-flow__node-input.selectable:focus-visible,
.react-flow__node-default.selectable.selected,
.react-flow__node-default.selectable:focus,
.react-flow__node-default.selectable:focus-visible,
.react-flow__node-output.selectable.selected,
.react-flow__node-output.selectable:focus,
.react-flow__node-output.selectable:focus-visible,
.react-flow__node-group.selectable.selected,
.react-flow__node-group.selectable:focus,
.react-flow__node-group.selectable:focus-visible {
box-shadow: var(
--xy-node-boxshadow-selected,
var(--xy-node-boxshadow-selected-default)
);
}
.react-flow__node-group {
background-color: var(
--xy-node-group-background-color,
var(--xy-node-group-background-color-default)
);
}
.react-flow__nodesselection-rect,
.react-flow__selection {
background: var(
--xy-selection-background-color,
var(--xy-selection-background-color-default)
);
border: var(--xy-selection-border, var(--xy-selection-border-default));
}
.react-flow__nodesselection-rect:focus,
.react-flow__nodesselection-rect:focus-visible,
.react-flow__selection:focus,
.react-flow__selection:focus-visible {
outline: none;
}
.react-flow__controls-button:hover {
background: var(
--xy-controls-button-background-color-hover-props,
var(
--xy-controls-button-background-color-hover,
var(--xy-controls-button-background-color-hover-default)
)
);
color: var(
--xy-controls-button-color-hover-props,
var(
--xy-controls-button-color-hover,
var(--xy-controls-button-color-hover-default)
)
);
}
.react-flow__controls-button:disabled {
pointer-events: none;
}
.react-flow__controls-button:disabled svg {
fill-opacity: 0.4;
}
.react-flow__controls-button:last-child {
border-bottom: none;
}
.react-flow__resize-control {
position: absolute;
}
.react-flow__resize-control.left,
.react-flow__resize-control.right {
cursor: ew-resize;
}
.react-flow__resize-control.top,
.react-flow__resize-control.bottom {
cursor: ns-resize;
}
.react-flow__resize-control.top.left,
.react-flow__resize-control.bottom.right {
cursor: nwse-resize;
}
.react-flow__resize-control.bottom.left,
.react-flow__resize-control.top.right {
cursor: nesw-resize;
}
/* handle styles */
.react-flow__resize-control.handle {
width: 4px;
height: 4px;
border: 1px solid #fff;
border-radius: 1px;
background-color: var(
--xy-resize-background-color,
var(--xy-resize-background-color-default)
);
transform: translate(-50%, -50%);
}
.react-flow__resize-control.handle.left {
left: 0;
top: 50%;
}
.react-flow__resize-control.handle.right {
left: 100%;
top: 50%;
}
.react-flow__resize-control.handle.top {
left: 50%;
top: 0;
}
.react-flow__resize-control.handle.bottom {
left: 50%;
top: 100%;
}
.react-flow__resize-control.handle.top.left {
left: 0;
}
.react-flow__resize-control.handle.bottom.left {
left: 0;
}
.react-flow__resize-control.handle.top.right {
left: 100%;
}
.react-flow__resize-control.handle.bottom.right {
left: 100%;
}
/* line styles */
.react-flow__resize-control.line {
border-color: var(
--xy-resize-background-color,
var(--xy-resize-background-color-default)
);
border-width: 0;
border-style: solid;
}
.react-flow__resize-control.line.left,
.react-flow__resize-control.line.right {
width: 1px;
transform: translate(-50%, 0);
top: 0;
height: 100%;
}
.react-flow__resize-control.line.left {
left: 0;
border-left-width: 1px;
}
.react-flow__resize-control.line.right {
left: 100%;
border-right-width: 1px;
}
.react-flow__resize-control.line.top,
.react-flow__resize-control.line.bottom {
height: 1px;
transform: translate(0, -50%);
left: 0;
width: 100%;
}
.react-flow__resize-control.line.top {
top: 0;
border-top-width: 1px;
}
.react-flow__resize-control.line.bottom {
border-bottom-width: 1px;
top: 100%;
}
.react-flow__edge-textbg {
fill: var(
--xy-edge-label-background-color,
var(--xy-edge-label-background-color-default)
);
}
.react-flow__edge-text {
fill: var(--xy-edge-label-color, var(--xy-edge-label-color-default));
}

View File

@@ -0,0 +1,56 @@
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils";
const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
));
AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className,
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View File

@@ -0,0 +1,136 @@
import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
const AlertDialog = AlertDialogPrimitive.Root;
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
const AlertDialogPortal = AlertDialogPrimitive.Portal;
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
ref={ref}
/>
));
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-neutral-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg dark:border-neutral-800 dark:bg-neutral-950",
className,
)}
{...props}
/>
</AlertDialogPortal>
));
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("flex flex-col space-y-2 text-center sm:text-left", className)}
{...props}
/>
);
AlertDialogHeader.displayName = "AlertDialogHeader";
const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className,
)}
{...props}
/>
);
AlertDialogFooter.displayName = "AlertDialogFooter";
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold", className)}
{...props}
/>
));
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
{...props}
/>
));
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName;
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)}
{...props}
/>
));
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
className,
)}
{...props}
/>
));
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
};

View File

@@ -0,0 +1,60 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const alertVariants = cva(
"relative w-full rounded-lg border border-neutral-200 p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-neutral-950 dark:border-neutral-800 dark:[&>svg]:text-neutral-50",
{
variants: {
variant: {
default:
"bg-white text-neutral-950 dark:bg-neutral-950 dark:text-neutral-50",
destructive:
"border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900",
},
},
defaultVariants: {
variant: "default",
},
},
);
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
));
Alert.displayName = "Alert";
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
));
AlertTitle.displayName = "AlertTitle";
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
));
AlertDescription.displayName = "AlertDescription";
export { Alert, AlertTitle, AlertDescription };

View File

@@ -0,0 +1,5 @@
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
const AspectRatio = AspectRatioPrimitive.Root;
export { AspectRatio };

View File

@@ -0,0 +1,48 @@
import * as React from "react";
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import { cn } from "@/lib/utils";
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className,
)}
{...props}
/>
));
Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-neutral-100 dark:bg-neutral-800",
className,
)}
{...props}
/>
));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
export { Avatar, AvatarImage, AvatarFallback };

View File

@@ -0,0 +1,36 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center rounded-full border border-neutral-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 dark:border-neutral-800 dark:focus:ring-neutral-300",
{
variants: {
variant: {
default:
"border-transparent bg-neutral-900 text-neutral-50 hover:bg-neutral-900/80 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/80",
secondary:
"border-transparent bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80",
destructive:
"border-transparent bg-red-500 text-neutral-50 hover:bg-red-500/80 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/80",
outline: "text-neutral-950 dark:text-neutral-50",
},
},
defaultVariants: {
variant: "default",
},
},
);
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}
export { Badge, badgeVariants };

View File

@@ -0,0 +1,118 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { ChevronRight, MoreHorizontal } from "lucide-react";
import { cn } from "@/lib/utils";
const Breadcrumb = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<"nav"> & {
separator?: React.ReactNode;
}
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
Breadcrumb.displayName = "Breadcrumb";
const BreadcrumbList = React.forwardRef<
HTMLOListElement,
React.ComponentPropsWithoutRef<"ol">
>(({ className, ...props }, ref) => (
<ol
ref={ref}
className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-sm text-neutral-500 sm:gap-2.5 dark:text-neutral-400",
className,
)}
{...props}
/>
));
BreadcrumbList.displayName = "BreadcrumbList";
const BreadcrumbItem = React.forwardRef<
HTMLLIElement,
React.ComponentPropsWithoutRef<"li">
>(({ className, ...props }, ref) => (
<li
ref={ref}
className={cn("inline-flex items-center gap-1.5", className)}
{...props}
/>
));
BreadcrumbItem.displayName = "BreadcrumbItem";
const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement,
React.ComponentPropsWithoutRef<"a"> & {
asChild?: boolean;
}
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a";
return (
<Comp
ref={ref}
className={cn(
"transition-colors hover:text-neutral-950 dark:hover:text-neutral-50",
className,
)}
{...props}
/>
);
});
BreadcrumbLink.displayName = "BreadcrumbLink";
const BreadcrumbPage = React.forwardRef<
HTMLSpanElement,
React.ComponentPropsWithoutRef<"span">
>(({ className, ...props }, ref) => (
<span
ref={ref}
role="link"
aria-disabled="true"
aria-current="page"
className={cn("font-normal text-neutral-950 dark:text-neutral-50", className)}
{...props}
/>
));
BreadcrumbPage.displayName = "BreadcrumbPage";
const BreadcrumbSeparator = ({
children,
className,
...props
}: React.ComponentProps<"li">) => (
<li
role="presentation"
aria-hidden="true"
className={cn("[&>svg]:size-3.5", className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
);
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
const BreadcrumbEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
<span
role="presentation"
aria-hidden="true"
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More</span>
</span>
);
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
};

View File

@@ -0,0 +1,59 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300",
{
variants: {
variant: {
default:
"bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90",
destructive:
"bg-red-500 text-neutral-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90",
outline:
"border border-neutral-200 bg-white hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50",
secondary:
"bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80",
ghost:
"hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50",
link:
"text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
},
);
Button.displayName = "Button";
export { Button, buttonVariants };

View File

@@ -0,0 +1,66 @@
import * as React from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { DayPicker } from "react-day-picker";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
function Calendar({
className,
classNames,
showOutsideDays = true,
...props
}: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn("p-3", className)}
classNames={{
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
month: "space-y-4",
caption: "flex justify-center pt-1 relative items-center",
caption_label: "text-sm font-medium",
nav: "space-x-1 flex items-center",
nav_button: cn(
buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
),
nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1",
table: "w-full border-collapse space-y-1",
head_row: "flex",
head_cell:
"text-neutral-500 rounded-md w-9 font-normal text-[0.8rem] dark:text-neutral-400",
row: "flex w-full mt-2",
cell:
"h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-neutral-100/50 [&:has([aria-selected])]:bg-neutral-100 first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20 dark:[&:has([aria-selected].day-outside)]:bg-neutral-800/50 dark:[&:has([aria-selected])]:bg-neutral-800",
day: cn(
buttonVariants({ variant: "ghost" }),
"h-9 w-9 p-0 font-normal aria-selected:opacity-100",
),
day_range_end: "day-range-end",
day_selected:
"bg-neutral-900 text-neutral-50 hover:bg-neutral-900 hover:text-neutral-50 focus:bg-neutral-900 focus:text-neutral-50 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50 dark:hover:text-neutral-900 dark:focus:bg-neutral-50 dark:focus:text-neutral-900",
day_today:
"bg-neutral-100 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-50",
day_outside:
"day-outside text-neutral-500 opacity-50 aria-selected:bg-neutral-100/50 aria-selected:text-neutral-500 aria-selected:opacity-30 dark:text-neutral-400 dark:aria-selected:bg-neutral-800/50 dark:aria-selected:text-neutral-400",
day_disabled: "text-neutral-500 opacity-50 dark:text-neutral-400",
day_range_middle:
"aria-selected:bg-neutral-100 aria-selected:text-neutral-900 dark:aria-selected:bg-neutral-800 dark:aria-selected:text-neutral-50",
day_hidden: "invisible",
...classNames,
}}
components={{
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
}}
{...props}
/>
);
}
Calendar.displayName = "Calendar";
export { Calendar };

View File

@@ -0,0 +1,86 @@
import * as React from "react";
import { cn } from "@/lib/utils";
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border border-neutral-200 bg-white text-neutral-950 shadow-sm dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
className,
)}
{...props}
/>
));
Card.displayName = "Card";
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
));
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className,
)}
{...props}
/>
));
CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
{...props}
/>
));
CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
));
CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
));
CardFooter.displayName = "CardFooter";
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};

View File

@@ -0,0 +1,260 @@
import * as React from "react";
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1];
type CarouselProps = {
opts?: CarouselOptions;
plugins?: CarouselPlugin;
orientation?: "horizontal" | "vertical";
setApi?: (api: CarouselApi) => void;
};
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
api: ReturnType<typeof useEmblaCarousel>[1];
scrollPrev: () => void;
scrollNext: () => void;
canScrollPrev: boolean;
canScrollNext: boolean;
} & CarouselProps;
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
function useCarousel() {
const context = React.useContext(CarouselContext);
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />");
}
return context;
}
const Carousel = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & CarouselProps
>(
(
{
orientation = "horizontal",
opts,
setApi,
plugins,
className,
children,
...props
},
ref,
) => {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins,
);
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
const [canScrollNext, setCanScrollNext] = React.useState(false);
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) {
return;
}
setCanScrollPrev(api.canScrollPrev());
setCanScrollNext(api.canScrollNext());
}, []);
const scrollPrev = React.useCallback(() => {
api?.scrollPrev();
}, [api]);
const scrollNext = React.useCallback(() => {
api?.scrollNext();
}, [api]);
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault();
scrollPrev();
} else if (event.key === "ArrowRight") {
event.preventDefault();
scrollNext();
}
},
[scrollPrev, scrollNext],
);
React.useEffect(() => {
if (!api || !setApi) {
return;
}
setApi(api);
}, [api, setApi]);
React.useEffect(() => {
if (!api) {
return;
}
onSelect(api);
api.on("reInit", onSelect);
api.on("select", onSelect);
return () => {
api?.off("select", onSelect);
};
}, [api, onSelect]);
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<div
ref={ref}
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
);
},
);
Carousel.displayName = "Carousel";
const CarouselContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel();
return (
<div ref={carouselRef} className="overflow-hidden">
<div
ref={ref}
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className,
)}
{...props}
/>
</div>
);
});
CarouselContent.displayName = "CarouselContent";
const CarouselItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { orientation } = useCarousel();
return (
<div
ref={ref}
role="group"
aria-roledescription="slide"
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className,
)}
{...props}
/>
);
});
CarouselItem.displayName = "CarouselItem";
const CarouselPrevious = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-left-12 top-1/2 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className,
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
>
<ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
);
});
CarouselPrevious.displayName = "CarouselPrevious";
const CarouselNext = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel();
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-right-12 top-1/2 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className,
)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}
>
<ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
);
});
CarouselNext.displayName = "CarouselNext";
export {
type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
};

View File

@@ -0,0 +1,361 @@
import * as React from "react";
import * as RechartsPrimitive from "recharts";
import { cn } from "@/lib/utils";
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const;
export type ChartConfig = {
[k in string]: {
label?: React.ReactNode;
icon?: React.ComponentType;
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
);
};
type ChartContextProps = {
config: ChartConfig;
};
const ChartContext = React.createContext<ChartContextProps | null>(null);
function useChart() {
const context = React.useContext(ChartContext);
if (!context) {
throw new Error("useChart must be used within a <ChartContainer />");
}
return context;
}
const ChartContainer = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
config: ChartConfig;
children: React.ComponentProps<
typeof RechartsPrimitive.ResponsiveContainer
>["children"];
}
>(({ id, className, children, config, ...props }, ref) => {
const uniqueId = React.useId();
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
return (
<ChartContext.Provider value={{ config }}>
<div
data-chart={chartId}
ref={ref}
className={cn(
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
className,
)}
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer>
{children}
</RechartsPrimitive.ResponsiveContainer>
</div>
</ChartContext.Provider>
);
});
ChartContainer.displayName = "Chart";
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(
([_, config]) => config.theme || config.color,
);
if (!colorConfig.length) {
return null;
}
return (
<style
dangerouslySetInnerHTML={{
__html: Object.entries(THEMES)
.map(
([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color =
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
itemConfig.color;
return color ? ` --color-${key}: ${color};` : null;
})
.join("\n")}
}
`,
)
.join("\n"),
}}
/>
);
};
const ChartTooltip = RechartsPrimitive.Tooltip;
const ChartTooltipContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
React.ComponentProps<"div"> & {
hideLabel?: boolean;
hideIndicator?: boolean;
indicator?: "line" | "dot" | "dashed";
nameKey?: string;
labelKey?: string;
}
>(
(
{
active,
payload,
className,
indicator = "dot",
hideLabel = false,
hideIndicator = false,
label,
labelFormatter,
labelClassName,
formatter,
color,
nameKey,
labelKey,
},
ref,
) => {
const { config } = useChart();
const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) {
return null;
}
const [item] = payload;
const key = `${labelKey || item.dataKey || item.name || "value"}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key);
const value =
!labelKey && typeof label === "string"
? config[label as keyof typeof config]?.label || label
: itemConfig?.label;
if (labelFormatter) {
return (
<div className={cn("font-medium", labelClassName)}>
{labelFormatter(value, payload)}
</div>
);
}
if (!value) {
return null;
}
return <div className={cn("font-medium", labelClassName)}>{value}</div>;
}, [
label,
labelFormatter,
payload,
hideLabel,
labelClassName,
config,
labelKey,
]);
if (!active || !payload?.length) {
return null;
}
const nestLabel = payload.length === 1 && indicator !== "dot";
return (
<div
ref={ref}
className={cn(
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-neutral-200 border-neutral-200/50 bg-white px-2.5 py-1.5 text-xs shadow-xl dark:border-neutral-800 dark:border-neutral-800/50 dark:bg-neutral-950",
className,
)}
>
{!nestLabel ? tooltipLabel : null}
<div className="grid gap-1.5">
{payload.map((item, index) => {
const key = `${nameKey || item.name || item.dataKey || "value"}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key);
const indicatorColor = color || item.payload.fill || item.color;
return (
<div
key={item.dataKey}
className={cn(
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-neutral-500 dark:[&>svg]:text-neutral-400",
indicator === "dot" && "items-center",
)}
>
{formatter && item?.value !== undefined && item.name ? (
formatter(item.value, item.name, item, index, item.payload)
) : (
<>
{itemConfig?.icon ? (
<itemConfig.icon />
) : (
!hideIndicator && (
<div
className={cn(
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
{
"h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line",
"w-0 border-[1.5px] border-dashed bg-transparent":
indicator === "dashed",
"my-0.5": nestLabel && indicator === "dashed",
},
)}
style={
{
"--color-bg": indicatorColor,
"--color-border": indicatorColor,
} as React.CSSProperties
}
/>
)
)}
<div
className={cn(
"flex flex-1 justify-between leading-none",
nestLabel ? "items-end" : "items-center",
)}
>
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-neutral-500 dark:text-neutral-400">
{itemConfig?.label || item.name}
</span>
</div>
{item.value && (
<span className="font-mono font-medium tabular-nums text-neutral-950 dark:text-neutral-50">
{item.value.toLocaleString()}
</span>
)}
</div>
</>
)}
</div>
);
})}
</div>
</div>
);
},
);
ChartTooltipContent.displayName = "ChartTooltip";
const ChartLegend = RechartsPrimitive.Legend;
const ChartLegendContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> &
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean;
nameKey?: string;
}
>(
(
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
ref,
) => {
const { config } = useChart();
if (!payload?.length) {
return null;
}
return (
<div
ref={ref}
className={cn(
"flex items-center justify-center gap-4",
verticalAlign === "top" ? "pb-3" : "pt-3",
className,
)}
>
{payload.map((item) => {
const key = `${nameKey || item.dataKey || "value"}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key);
return (
<div
key={item.value}
className={cn(
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-neutral-500 dark:[&>svg]:text-neutral-400",
)}
>
{itemConfig?.icon && !hideIcon ? (
<itemConfig.icon />
) : (
<div
className="h-2 w-2 shrink-0 rounded-[2px]"
style={{
backgroundColor: item.color,
}}
/>
)}
{itemConfig?.label}
</div>
);
})}
</div>
);
},
);
ChartLegendContent.displayName = "ChartLegend";
// Helper to extract item config from a payload.
function getPayloadConfigFromPayload(
config: ChartConfig,
payload: unknown,
key: string,
) {
if (typeof payload !== "object" || payload === null) {
return undefined;
}
const payloadPayload =
"payload" in payload &&
typeof payload.payload === "object" &&
payload.payload !== null
? payload.payload
: undefined;
let configLabelKey: string = key;
if (
key in payload &&
typeof payload[key as keyof typeof payload] === "string"
) {
configLabelKey = payload[key as keyof typeof payload] as string;
} else if (
payloadPayload &&
key in payloadPayload &&
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
) {
configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;
}
return configLabelKey in config
? config[configLabelKey]
: config[key as keyof typeof config];
}
export {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
ChartStyle,
};

View File

@@ -0,0 +1,28 @@
import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { Check } from "lucide-react";
import { cn } from "@/lib/utils";
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-neutral-200 border-neutral-900 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-neutral-900 data-[state=checked]:text-neutral-50 dark:border-neutral-800 dark:border-neutral-50 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300 dark:data-[state=checked]:bg-neutral-50 dark:data-[state=checked]:text-neutral-900",
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
export { Checkbox };

View File

@@ -0,0 +1,9 @@
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleTrigger, CollapsibleContent };

View File

@@ -0,0 +1,153 @@
import * as React from "react";
import { type DialogProps } from "@radix-ui/react-dialog";
import { Command as CommandPrimitive } from "cmdk";
import { Search } from "lucide-react";
import { cn } from "@/lib/utils";
import { Dialog, DialogContent } from "@/components/ui/dialog";
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-white text-neutral-950 dark:bg-neutral-950 dark:text-neutral-50",
className,
)}
{...props}
/>
));
Command.displayName = CommandPrimitive.displayName;
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0 shadow-lg">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-neutral-500 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5 dark:[&_[cmdk-group-heading]]:text-neutral-400">
{children}
</Command>
</DialogContent>
</Dialog>
);
};
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-neutral-500 disabled:cursor-not-allowed disabled:opacity-50 dark:placeholder:text-neutral-400",
className,
)}
{...props}
/>
</div>
));
CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
));
CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
));
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-neutral-950 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-neutral-500 dark:text-neutral-50 dark:[&_[cmdk-group-heading]]:text-neutral-400",
className,
)}
{...props}
/>
));
CommandGroup.displayName = CommandPrimitive.Group.displayName;
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn("-mx-1 h-px bg-neutral-200 dark:bg-neutral-800", className)}
{...props}
/>
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-neutral-100 data-[selected=true]:text-neutral-900 data-[disabled=true]:opacity-50 dark:data-[selected='true']:bg-neutral-800 dark:data-[selected=true]:text-neutral-50",
className,
)}
{...props}
/>
));
CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-neutral-500 dark:text-neutral-400",
className,
)}
{...props}
/>
);
};
CommandShortcut.displayName = "CommandShortcut";
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
};

View File

@@ -0,0 +1,201 @@
import * as React from "react";
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/lib/utils";
const ContextMenu = ContextMenuPrimitive.Root;
const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
const ContextMenuGroup = ContextMenuPrimitive.Group;
const ContextMenuPortal = ContextMenuPrimitive.Portal;
const ContextMenuSub = ContextMenuPrimitive.Sub;
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
const ContextMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-50 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-50",
inset && "pl-8",
className,
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger>
));
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
const ContextMenuSubContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
className,
)}
{...props}
/>
));
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
className,
)}
{...props}
/>
</ContextMenuPrimitive.Portal>
));
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
const ContextMenuItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
inset && "pl-8",
className,
)}
{...props}
/>
));
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<ContextMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
className,
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
));
ContextMenuCheckboxItem.displayName =
ContextMenuPrimitive.CheckboxItem.displayName;
const ContextMenuRadioItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
className,
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
));
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold text-neutral-950 dark:text-neutral-50",
inset && "pl-8",
className,
)}
{...props}
/>
));
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn(
"-mx-1 my-1 h-px bg-neutral-200 dark:bg-neutral-800",
className,
)}
{...props}
/>
));
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
const ContextMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-neutral-500 dark:text-neutral-400",
className,
)}
{...props}
/>
);
};
ContextMenuShortcut.displayName = "ContextMenuShortcut";
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
};

View File

@@ -0,0 +1,117 @@
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-neutral-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg dark:border-neutral-800 dark:bg-neutral-950",
className,
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-500 dark:ring-offset-neutral-950 dark:focus:ring-neutral-300 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-400">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className,
)}
{...props}
/>
);
DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className,
)}
{...props}
/>
);
DialogFooter.displayName = "DialogFooter";
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
{...props}
/>
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
};

View File

@@ -0,0 +1,110 @@
import * as React from "react";
import { Drawer as DrawerPrimitive } from "vaul";
import { cn } from "@/lib/utils";
const Drawer = ({
shouldScaleBackground = true,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root
shouldScaleBackground={shouldScaleBackground}
{...props}
/>
);
Drawer.displayName = "Drawer";
const DrawerTrigger = DrawerPrimitive.Trigger;
const DrawerPortal = DrawerPrimitive.Portal;
const DrawerClose = DrawerPrimitive.Close;
const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay
ref={ref}
className={cn("fixed inset-0 z-50 bg-black/80", className)}
{...props}
/>
));
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-950",
className,
)}
{...props}
>
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-neutral-100 dark:bg-neutral-800" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
));
DrawerContent.displayName = "DrawerContent";
const DrawerHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
{...props}
/>
);
DrawerHeader.displayName = "DrawerHeader";
const DrawerFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />
);
DrawerFooter.displayName = "DrawerFooter";
const DrawerTitle = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...props}
/>
));
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Description
ref={ref}
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
{...props}
/>
));
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
};

View File

@@ -0,0 +1,201 @@
import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/lib/utils";
const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 data-[state=open]:bg-neutral-100 dark:focus:bg-neutral-800 dark:data-[state=open]:bg-neutral-800",
inset && "pl-8",
className,
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
));
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
className,
)}
{...props}
/>
));
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
className,
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
inset && "pl-8",
className,
)}
{...props}
/>
));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
className,
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
));
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
className,
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className,
)}
{...props}
/>
));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn(
"-mx-1 my-1 h-px bg-neutral-100 dark:bg-neutral-800",
className,
)}
{...props}
/>
));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
);
};
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
};

View File

@@ -0,0 +1,177 @@
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form";
import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label";
const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName;
};
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue,
);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
};
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>");
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
type FormItemContextValue = {
id: string;
};
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue,
);
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
);
});
FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField();
return (
<Label
ref={ref}
className={cn(error && "text-red-500 dark:text-red-900", className)}
htmlFor={formItemId}
{...props}
/>
);
});
FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
);
});
FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
{...props}
/>
);
});
FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
if (!body) {
return null;
}
return (
<p
ref={ref}
id={formMessageId}
className={cn(
"text-sm font-medium text-red-500 dark:text-red-900",
className,
)}
{...props}
>
{body}
</p>
);
});
FormMessage.displayName = "FormMessage";
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
};

View File

@@ -0,0 +1,27 @@
import * as React from "react";
import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
import { cn } from "@/lib/utils";
const HoverCard = HoverCardPrimitive.Root;
const HoverCardTrigger = HoverCardPrimitive.Trigger;
const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-64 rounded-md border border-neutral-200 bg-white p-4 text-neutral-950 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
className,
)}
{...props}
/>
));
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
export { HoverCard, HoverCardTrigger, HoverCardContent };

View File

@@ -0,0 +1,70 @@
import * as React from "react";
import { OTPInput, OTPInputContext } from "input-otp";
import { Dot } from "lucide-react";
import { cn } from "@/lib/utils";
const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>,
React.ComponentPropsWithoutRef<typeof OTPInput>
>(({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn(
"flex items-center gap-2 has-[:disabled]:opacity-50",
containerClassName,
)}
className={cn("disabled:cursor-not-allowed", className)}
{...props}
/>
));
InputOTP.displayName = "InputOTP";
const InputOTPGroup = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div">
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center", className)} {...props} />
));
InputOTPGroup.displayName = "InputOTPGroup";
const InputOTPSlot = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div"> & { index: number }
>(({ index, className, ...props }, ref) => {
const inputOTPContext = React.useContext(OTPInputContext);
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];
return (
<div
ref={ref}
className={cn(
"relative flex h-10 w-10 items-center justify-center border-y border-r border-neutral-200 text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md dark:border-neutral-800",
isActive &&
"z-10 ring-2 ring-neutral-950 ring-offset-white dark:ring-neutral-300 dark:ring-offset-neutral-950",
className,
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="h-4 w-px animate-caret-blink bg-neutral-950 duration-1000 dark:bg-neutral-50" />
</div>
)}
</div>
);
});
InputOTPSlot.displayName = "InputOTPSlot";
const InputOTPSeparator = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div">
>(({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
<Dot />
</div>
));
InputOTPSeparator.displayName = "InputOTPSeparator";
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };

View File

@@ -0,0 +1,25 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-neutral-200 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-neutral-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-800 dark:bg-neutral-950 dark:ring-offset-neutral-950 dark:placeholder:text-neutral-400 dark:focus-visible:ring-neutral-300",
className,
)}
ref={ref}
{...props}
/>
);
},
);
Input.displayName = "Input";
export { Input };

View File

@@ -0,0 +1,24 @@
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
);
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
));
Label.displayName = LabelPrimitive.Root.displayName;
export { Label };

View File

@@ -0,0 +1,237 @@
import * as React from "react";
import * as MenubarPrimitive from "@radix-ui/react-menubar";
import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/lib/utils";
const MenubarMenu = MenubarPrimitive.Menu;
const MenubarGroup = MenubarPrimitive.Group;
const MenubarPortal = MenubarPrimitive.Portal;
const MenubarSub = MenubarPrimitive.Sub;
const MenubarRadioGroup = MenubarPrimitive.RadioGroup;
const Menubar = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Root
ref={ref}
className={cn(
"flex h-10 items-center space-x-1 rounded-md border border-neutral-200 bg-white p-1 dark:border-neutral-800 dark:bg-neutral-950",
className,
)}
{...props}
/>
));
Menubar.displayName = MenubarPrimitive.Root.displayName;
const MenubarTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Trigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-50 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-50",
className,
)}
{...props}
/>
));
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;
const MenubarSubTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<MenubarPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-50 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-50",
inset && "pl-8",
className,
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger>
));
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;
const MenubarSubContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
className,
)}
{...props}
/>
));
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;
const MenubarContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
>(
(
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
ref,
) => (
<MenubarPrimitive.Portal>
<MenubarPrimitive.Content
ref={ref}
align={align}
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[12rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
className,
)}
{...props}
/>
</MenubarPrimitive.Portal>
),
);
MenubarContent.displayName = MenubarPrimitive.Content.displayName;
const MenubarItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
inset && "pl-8",
className,
)}
{...props}
/>
));
MenubarItem.displayName = MenubarPrimitive.Item.displayName;
const MenubarCheckboxItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<MenubarPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
className,
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.CheckboxItem>
));
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;
const MenubarRadioItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<MenubarPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
className,
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.RadioItem>
));
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;
const MenubarLabel = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className,
)}
{...props}
/>
));
MenubarLabel.displayName = MenubarPrimitive.Label.displayName;
const MenubarSeparator = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Separator
ref={ref}
className={cn(
"-mx-1 my-1 h-px bg-neutral-100 dark:bg-neutral-800",
className,
)}
{...props}
/>
));
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
const MenubarShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-neutral-500 dark:text-neutral-400",
className,
)}
{...props}
/>
);
};
MenubarShortcut.displayname = "MenubarShortcut";
export {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
MenubarSeparator,
MenubarLabel,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarPortal,
MenubarSubContent,
MenubarSubTrigger,
MenubarGroup,
MenubarSub,
MenubarShortcut,
};

View File

@@ -0,0 +1,129 @@
import * as React from "react";
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
import { cva } from "class-variance-authority";
import { ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils";
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn(
"relative z-10 flex max-w-max flex-1 items-center justify-center",
className,
)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
));
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
"group flex flex-1 list-none items-center justify-center space-x-1",
className,
)}
{...props}
/>
));
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
const NavigationMenuItem = NavigationMenuPrimitive.Item;
const navigationMenuTriggerStyle = cva(
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-white px-4 py-2 text-sm font-medium transition-colors hover:bg-neutral-100 hover:text-neutral-900 focus:bg-neutral-100 focus:text-neutral-900 focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-neutral-100/50 data-[state=open]:bg-neutral-100/50 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50 dark:data-[active]:bg-neutral-800/50 dark:data-[state=open]:bg-neutral-800/50",
);
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}
{""}
<ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
));
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto",
className,
)}
{...props}
/>
));
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
const NavigationMenuLink = NavigationMenuPrimitive.Link;
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border border-neutral-200 bg-white text-neutral-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)] dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
className,
)}
ref={ref}
{...props}
/>
</div>
));
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName;
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className,
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-neutral-200 shadow-md dark:bg-neutral-800" />
</NavigationMenuPrimitive.Indicator>
));
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName;
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
};

View File

@@ -0,0 +1,117 @@
import * as React from "react";
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
import { cn } from "@/lib/utils";
import { ButtonProps, buttonVariants } from "@/components/ui/button";
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav
role="navigation"
aria-label="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
{...props}
/>
);
Pagination.displayName = "Pagination";
const PaginationContent = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
ref={ref}
className={cn("flex flex-row items-center gap-1", className)}
{...props}
/>
));
PaginationContent.displayName = "PaginationContent";
const PaginationItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
<li ref={ref} className={cn("", className)} {...props} />
));
PaginationItem.displayName = "PaginationItem";
type PaginationLinkProps = {
isActive?: boolean;
} & Pick<ButtonProps, "size"> &
React.ComponentProps<"a">;
const PaginationLink = ({
className,
isActive,
size = "icon",
...props
}: PaginationLinkProps) => (
<a
aria-current={isActive ? "page" : undefined}
className={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
size,
}),
className,
)}
{...props}
/>
);
PaginationLink.displayName = "PaginationLink";
const PaginationPrevious = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 pl-2.5", className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<span>Previous</span>
</PaginationLink>
);
PaginationPrevious.displayName = "PaginationPrevious";
const PaginationNext = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 pr-2.5", className)}
{...props}
>
<span>Next</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
);
PaginationNext.displayName = "PaginationNext";
const PaginationEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
<span
aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
);
PaginationEllipsis.displayName = "PaginationEllipsis";
export {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
};

Some files were not shown because too many files have changed in this diff Show More